aboutsummaryrefslogtreecommitdiff
path: root/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp')
-rw-r--r--clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp367
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