diff options
Diffstat (limited to 'clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp')
-rw-r--r-- | clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp | 367 |
1 files changed, 263 insertions, 104 deletions
diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp index eef3cc813a4a..308dc25dad1f 100644 --- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp +++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp @@ -18,15 +18,19 @@ #include "clang/AST/ExprCXX.h" #include "clang/AST/Stmt.h" #include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Analysis/CFG.h" +#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h" #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" -#include "clang/Analysis/FlowSensitive/MatchSwitch.h" #include "clang/Analysis/FlowSensitive/NoopLattice.h" +#include "clang/Analysis/FlowSensitive/StorageLocation.h" #include "clang/Analysis/FlowSensitive/Value.h" #include "clang/Basic/SourceLocation.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" +#include "llvm/Support/ErrorHandling.h" #include <cassert> #include <memory> +#include <optional> #include <utility> #include <vector> @@ -55,7 +59,7 @@ auto hasOptionalType() { return hasType(optionalOrAliasType()); } auto isOptionalMemberCallWithName( llvm::StringRef MemberName, - llvm::Optional<StatementMatcher> Ignorable = llvm::None) { + const std::optional<StatementMatcher> &Ignorable = std::nullopt) { auto Exception = unless(Ignorable ? expr(anyOf(*Ignorable, cxxThisExpr())) : cxxThisExpr()); return cxxMemberCallExpr( @@ -65,7 +69,7 @@ auto isOptionalMemberCallWithName( auto isOptionalOperatorCallWithName( llvm::StringRef operator_name, - llvm::Optional<StatementMatcher> Ignorable = llvm::None) { + const std::optional<StatementMatcher> &Ignorable = std::nullopt) { return cxxOperatorCallExpr( hasOverloadedOperatorName(operator_name), callee(cxxMethodDecl(ofClass(optionalClass()))), @@ -79,19 +83,30 @@ 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")); } auto isOptionalNulloptConstructor() { - return cxxConstructExpr(hasOptionalType(), argumentCountIs(1), - hasArgument(0, hasNulloptType())); + return cxxConstructExpr( + hasOptionalType(), + hasDeclaration(cxxConstructorDecl(parameterCountIs(1), + hasParameter(0, hasNulloptType())))); } auto isOptionalInPlaceConstructor() { @@ -116,6 +131,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()))), @@ -171,6 +191,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) { @@ -207,7 +248,7 @@ QualType stripReference(QualType Type) { } /// Returns true if and only if `Type` is an optional type. -bool IsOptionalType(QualType Type) { +bool isOptionalType(QualType Type) { if (!Type->isRecordType()) return false; // FIXME: Optimize this by avoiding the `getQualifiedNameAsString` call. @@ -221,7 +262,7 @@ bool IsOptionalType(QualType Type) { /// For example, if `Type` is `optional<optional<int>>`, the result of this /// function will be 2. int countOptionalWrappers(const ASTContext &ASTCtx, QualType Type) { - if (!IsOptionalType(Type)) + if (!isOptionalType(Type)) return 0; return 1 + countOptionalWrappers( ASTCtx, @@ -356,16 +397,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, @@ -410,21 +443,21 @@ void transferCallReturningOptional(const CallExpr *E, Loc, createOptionalValue(State.Env, State.Env.makeAtomicBoolValue())); } -void assignOptionalValue(const Expr &E, LatticeTransferState &State, +void assignOptionalValue(const Expr &E, Environment &Env, BoolValue &HasValueVal) { if (auto *OptionalLoc = - State.Env.getStorageLocation(E, SkipPast::ReferenceThenPointer)) { - State.Env.setValue(*OptionalLoc, - createOptionalValue(State.Env, HasValueVal)); + Env.getStorageLocation(E, SkipPast::ReferenceThenPointer)) { + Env.setValue(*OptionalLoc, createOptionalValue(Env, HasValueVal)); } } /// 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() != nullptr); assert(F.getTemplateSpecializationArgs()->size() > 0); const int TemplateParamOptionalWrappersCount = countOptionalWrappers( @@ -451,10 +484,10 @@ void transferValueOrConversionConstructor( LatticeTransferState &State) { assert(E->getNumArgs() > 0); - assignOptionalValue(*E, State, - value_orConversionHasValue(*E->getConstructor(), - *E->getArg(0), MatchRes, - State)); + assignOptionalValue(*E, State.Env, + valueOrConversionHasValue(*E->getConstructor(), + *E->getArg(0), MatchRes, + State)); } void transferAssignment(const CXXOperatorCallExpr *E, BoolValue &HasValueVal, @@ -477,8 +510,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); } @@ -532,123 +565,209 @@ void transferStdSwapCall(const CallExpr *E, const MatchFinder::MatchResult &, transferSwap(*OptionalLoc1, *OptionalLoc2, State); } -llvm::Optional<StatementMatcher> +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))); + } +} + +std::optional<StatementMatcher> ignorableOptional(const UncheckedOptionalAccessModelOptions &Options) { - if (Options.IgnoreSmartPointerDereference) - return memberExpr(hasObjectExpression(ignoringParenImpCasts( - cxxOperatorCallExpr(anyOf(hasOverloadedOperatorName("->"), - hasOverloadedOperatorName("*")), - unless(hasArgument(0, expr(hasOptionalType()))))))); - return llvm::None; + if (Options.IgnoreSmartPointerDereference) { + auto SmartPtrUse = expr(ignoringParenImpCasts(cxxOperatorCallExpr( + anyOf(hasOverloadedOperatorName("->"), hasOverloadedOperatorName("*")), + unless(hasArgument(0, expr(hasOptionalType())))))); + return expr( + anyOf(SmartPtrUse, memberExpr(hasObjectExpression(SmartPtrUse)))); + } + return std::nullopt; } StatementMatcher -valueCall(llvm::Optional<StatementMatcher> &IgnorableOptional) { +valueCall(const std::optional<StatementMatcher> &IgnorableOptional) { return isOptionalMemberCallWithName("value", IgnorableOptional); } StatementMatcher -valueOperatorCall(llvm::Optional<StatementMatcher> &IgnorableOptional) { +valueOperatorCall(const std::optional<StatementMatcher> &IgnorableOptional) { return expr(anyOf(isOptionalOperatorCallWithName("*", IgnorableOptional), isOptionalOperatorCallWithName("->", IgnorableOptional))); } -auto buildTransferMatchSwitch( - const UncheckedOptionalAccessModelOptions &Options) { +auto buildTransferMatchSwitch() { // FIXME: Evaluate the efficiency of matchers. If using matchers results in a // lot of duplicated work (e.g. string comparisons), consider providing APIs // that avoid it through memoization. - auto IgnorableOptional = ignorableOptional(Options); - return MatchSwitchBuilder<LatticeTransferState>() + return CFGMatchSwitchBuilder<LatticeTransferState>() // Attach a symbolic "has_value" state to optional values that we see for // the first time. - .CaseOf<Expr>( + .CaseOfCFGStmt<Expr>( expr(anyOf(declRefExpr(), memberExpr()), hasOptionalType()), initializeOptionalReference) // make_optional - .CaseOf<CallExpr>(isMakeOptionalCall(), transferMakeOptionalCall) + .CaseOfCFGStmt<CallExpr>(isMakeOptionalCall(), transferMakeOptionalCall) - // optional::optional - .CaseOf<CXXConstructExpr>( + // optional::optional (in place) + .CaseOfCFGStmt<CXXConstructExpr>( isOptionalInPlaceConstructor(), [](const CXXConstructExpr *E, const MatchFinder::MatchResult &, LatticeTransferState &State) { - assignOptionalValue(*E, State, State.Env.getBoolLiteralValue(true)); + assignOptionalValue(*E, State.Env, + State.Env.getBoolLiteralValue(true)); + }) + // nullopt_t::nullopt_t + .CaseOfCFGStmt<CXXConstructExpr>( + isNulloptConstructor(), + [](const CXXConstructExpr *E, const MatchFinder::MatchResult &, + LatticeTransferState &State) { + assignOptionalValue(*E, State.Env, + State.Env.getBoolLiteralValue(false)); }) - .CaseOf<CXXConstructExpr>( + // optional::optional(nullopt_t) + .CaseOfCFGStmt<CXXConstructExpr>( isOptionalNulloptConstructor(), [](const CXXConstructExpr *E, const MatchFinder::MatchResult &, LatticeTransferState &State) { - assignOptionalValue(*E, State, + assignOptionalValue(*E, State.Env, State.Env.getBoolLiteralValue(false)); }) - .CaseOf<CXXConstructExpr>(isOptionalValueOrConversionConstructor(), - transferValueOrConversionConstructor) + // optional::optional (value/conversion) + .CaseOfCFGStmt<CXXConstructExpr>(isOptionalValueOrConversionConstructor(), + transferValueOrConversionConstructor) + // optional::operator= - .CaseOf<CXXOperatorCallExpr>(isOptionalValueOrConversionAssignment(), - transferValueOrConversionAssignment) - .CaseOf<CXXOperatorCallExpr>(isOptionalNulloptAssignment(), - transferNulloptAssignment) + .CaseOfCFGStmt<CXXOperatorCallExpr>( + isOptionalValueOrConversionAssignment(), + transferValueOrConversionAssignment) + .CaseOfCFGStmt<CXXOperatorCallExpr>(isOptionalNulloptAssignment(), + transferNulloptAssignment) // optional::value - .CaseOf<CXXMemberCallExpr>( - valueCall(IgnorableOptional), + .CaseOfCFGStmt<CXXMemberCallExpr>( + valueCall(std::nullopt), [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, LatticeTransferState &State) { transferUnwrapCall(E, E->getImplicitObjectArgument(), State); }) // optional::operator*, optional::operator-> - .CaseOf<CallExpr>(valueOperatorCall(IgnorableOptional), - [](const CallExpr *E, const MatchFinder::MatchResult &, - LatticeTransferState &State) { - transferUnwrapCall(E, E->getArg(0), State); - }) + .CaseOfCFGStmt<CallExpr>(valueOperatorCall(std::nullopt), + [](const CallExpr *E, + const MatchFinder::MatchResult &, + LatticeTransferState &State) { + transferUnwrapCall(E, E->getArg(0), State); + }) // optional::has_value - .CaseOf<CXXMemberCallExpr>(isOptionalMemberCallWithName("has_value"), - transferOptionalHasValueCall) + .CaseOfCFGStmt<CXXMemberCallExpr>( + isOptionalMemberCallWithName("has_value"), + transferOptionalHasValueCall) // optional::operator bool - .CaseOf<CXXMemberCallExpr>(isOptionalMemberCallWithName("operator bool"), - transferOptionalHasValueCall) + .CaseOfCFGStmt<CXXMemberCallExpr>( + isOptionalMemberCallWithName("operator bool"), + transferOptionalHasValueCall) // optional::emplace - .CaseOf<CXXMemberCallExpr>( + .CaseOfCFGStmt<CXXMemberCallExpr>( isOptionalMemberCallWithName("emplace"), [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, LatticeTransferState &State) { - assignOptionalValue(*E->getImplicitObjectArgument(), State, + assignOptionalValue(*E->getImplicitObjectArgument(), State.Env, State.Env.getBoolLiteralValue(true)); }) // optional::reset - .CaseOf<CXXMemberCallExpr>( + .CaseOfCFGStmt<CXXMemberCallExpr>( isOptionalMemberCallWithName("reset"), [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, LatticeTransferState &State) { - assignOptionalValue(*E->getImplicitObjectArgument(), State, + assignOptionalValue(*E->getImplicitObjectArgument(), State.Env, State.Env.getBoolLiteralValue(false)); }) // optional::swap - .CaseOf<CXXMemberCallExpr>(isOptionalMemberCallWithName("swap"), - transferSwapCall) + .CaseOfCFGStmt<CXXMemberCallExpr>(isOptionalMemberCallWithName("swap"), + transferSwapCall) // std::swap - .CaseOf<CallExpr>(isStdSwapCall(), transferStdSwapCall) + .CaseOfCFGStmt<CallExpr>(isStdSwapCall(), transferStdSwapCall) // opt.value_or("").empty() - .CaseOf<Expr>(isValueOrStringEmptyCall(), transferValueOrStringEmptyCall) + .CaseOfCFGStmt<Expr>(isValueOrStringEmptyCall(), + transferValueOrStringEmptyCall) // opt.value_or(X) != X - .CaseOf<Expr>(isValueOrNotEqX(), transferValueOrNotEqX) + .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 - .CaseOf<CallExpr>(isCallReturningOptional(), - transferCallReturningOptional) + .CaseOfCFGStmt<CallExpr>(isCallReturningOptional(), + transferCallReturningOptional) .Build(); } @@ -677,9 +796,9 @@ auto buildDiagnoseMatchSwitch( // lot of duplicated work (e.g. string comparisons), consider providing APIs // that avoid it through memoization. auto IgnorableOptional = ignorableOptional(Options); - return MatchSwitchBuilder<const Environment, std::vector<SourceLocation>>() + return CFGMatchSwitchBuilder<const Environment, std::vector<SourceLocation>>() // optional::value - .CaseOf<CXXMemberCallExpr>( + .CaseOfCFGStmt<CXXMemberCallExpr>( valueCall(IgnorableOptional), [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, const Environment &Env) { @@ -687,7 +806,7 @@ auto buildDiagnoseMatchSwitch( }) // optional::operator*, optional::operator-> - .CaseOf<CallExpr>( + .CaseOfCFGStmt<CallExpr>( valueOperatorCall(IgnorableOptional), [](const CallExpr *E, const MatchFinder::MatchResult &, const Environment &Env) { @@ -703,23 +822,31 @@ UncheckedOptionalAccessModel::optionalClassDecl() { return optionalClass(); } -UncheckedOptionalAccessModel::UncheckedOptionalAccessModel( - ASTContext &Ctx, UncheckedOptionalAccessModelOptions Options) +UncheckedOptionalAccessModel::UncheckedOptionalAccessModel(ASTContext &Ctx) : DataflowAnalysis<UncheckedOptionalAccessModel, NoopLattice>(Ctx), - TransferMatchSwitch(buildTransferMatchSwitch(Options)) {} + TransferMatchSwitch(buildTransferMatchSwitch()) {} -void UncheckedOptionalAccessModel::transfer(const Stmt *S, NoopLattice &L, - Environment &Env) { +void UncheckedOptionalAccessModel::transfer(const CFGElement *Elt, + NoopLattice &L, Environment &Env) { LatticeTransferState State(L, Env); - TransferMatchSwitch(*S, getASTContext(), State); -} - -bool UncheckedOptionalAccessModel::compareEquivalent(QualType Type, - const Value &Val1, - const Environment &Env1, - const Value &Val2, - const Environment &Env2) { - return isNonEmptyOptional(Val1, Env1) == isNonEmptyOptional(Val2, Env2); + TransferMatchSwitch(*Elt, getASTContext(), State); +} + +ComparisonResult UncheckedOptionalAccessModel::compare( + QualType Type, const Value &Val1, const Environment &Env1, + const Value &Val2, const Environment &Env2) { + if (!isOptionalType(Type)) + return ComparisonResult::Unknown; + 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; } bool UncheckedOptionalAccessModel::merge(QualType Type, const Value &Val1, @@ -728,25 +855,57 @@ bool UncheckedOptionalAccessModel::merge(QualType Type, const Value &Val1, const Environment &Env2, Value &MergedVal, Environment &MergedEnv) { - if (!IsOptionalType(Type)) + 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<BoolValue>(Prev.getProperty("has_value"))) { + if (isa<TopBoolValue>(PrevHasVal)) + return &Prev; + } + if (auto *CurrentHasVal = + cast_or_null<BoolValue>(Current.getProperty("has_value"))) { + if (isa<TopBoolValue>(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)) {} std::vector<SourceLocation> UncheckedOptionalAccessDiagnoser::diagnose( - ASTContext &Context, const Stmt *Stmt, const Environment &Env) { - return DiagnoseMatchSwitch(*Stmt, Context, Env); + ASTContext &Ctx, const CFGElement *Elt, const Environment &Env) { + return DiagnoseMatchSwitch(*Elt, Ctx, Env); } } // namespace dataflow |