diff options
| author | Dimitry Andric <dim@FreeBSD.org> | 2022-07-03 14:10:23 +0000 |
|---|---|---|
| committer | Dimitry Andric <dim@FreeBSD.org> | 2022-07-03 14:10:23 +0000 |
| commit | 145449b1e420787bb99721a429341fa6be3adfb6 (patch) | |
| tree | 1d56ae694a6de602e348dd80165cf881a36600ed /clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp | |
| parent | ecbca9f5fb7d7613d2b94982c4825eb0d33d6842 (diff) | |
Diffstat (limited to 'clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp')
| -rw-r--r-- | clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp | 753 |
1 files changed, 753 insertions, 0 deletions
diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp new file mode 100644 index 000000000000..eef3cc813a4a --- /dev/null +++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp @@ -0,0 +1,753 @@ +//===-- UncheckedOptionalAccessModel.cpp ------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines a dataflow analysis that detects unsafe uses of optional +// values. +// +//===----------------------------------------------------------------------===// + +#include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/Stmt.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" +#include "clang/Analysis/FlowSensitive/MatchSwitch.h" +#include "clang/Analysis/FlowSensitive/NoopLattice.h" +#include "clang/Analysis/FlowSensitive/Value.h" +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include <cassert> +#include <memory> +#include <utility> +#include <vector> + +namespace clang { +namespace dataflow { +namespace { + +using namespace ::clang::ast_matchers; +using LatticeTransferState = TransferState<NoopLattice>; + +DeclarationMatcher optionalClass() { + return classTemplateSpecializationDecl( + anyOf(hasName("std::optional"), hasName("std::__optional_storage_base"), + hasName("__optional_destruct_base"), hasName("absl::optional"), + hasName("base::Optional")), + hasTemplateArgument(0, refersToType(type().bind("T")))); +} + +auto optionalOrAliasType() { + return hasUnqualifiedDesugaredType( + recordType(hasDeclaration(optionalClass()))); +} + +/// Matches any of the spellings of the optional types and sugar, aliases, etc. +auto hasOptionalType() { return hasType(optionalOrAliasType()); } + +auto isOptionalMemberCallWithName( + llvm::StringRef MemberName, + llvm::Optional<StatementMatcher> Ignorable = llvm::None) { + auto Exception = unless(Ignorable ? expr(anyOf(*Ignorable, cxxThisExpr())) + : cxxThisExpr()); + return cxxMemberCallExpr( + on(expr(Exception)), + callee(cxxMethodDecl(hasName(MemberName), ofClass(optionalClass())))); +} + +auto isOptionalOperatorCallWithName( + llvm::StringRef operator_name, + llvm::Optional<StatementMatcher> Ignorable = llvm::None) { + return cxxOperatorCallExpr( + hasOverloadedOperatorName(operator_name), + callee(cxxMethodDecl(ofClass(optionalClass()))), + Ignorable ? callExpr(unless(hasArgument(0, *Ignorable))) : callExpr()); +} + +auto isMakeOptionalCall() { + return callExpr( + callee(functionDecl(hasAnyName( + "std::make_optional", "base::make_optional", "absl::make_optional"))), + hasOptionalType()); +} + +auto hasNulloptType() { + return hasType(namedDecl( + hasAnyName("std::nullopt_t", "absl::nullopt_t", "base::nullopt_t"))); +} + +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())); +} + +auto isOptionalInPlaceConstructor() { + return cxxConstructExpr(hasOptionalType(), + hasArgument(0, hasType(inPlaceClass()))); +} + +auto isOptionalValueOrConversionConstructor() { + return cxxConstructExpr( + hasOptionalType(), + unless(hasDeclaration( + cxxConstructorDecl(anyOf(isCopyConstructor(), isMoveConstructor())))), + argumentCountIs(1), hasArgument(0, unless(hasNulloptType()))); +} + +auto isOptionalValueOrConversionAssignment() { + return cxxOperatorCallExpr( + hasOverloadedOperatorName("="), + callee(cxxMethodDecl(ofClass(optionalClass()))), + unless(hasDeclaration(cxxMethodDecl( + anyOf(isCopyAssignmentOperator(), isMoveAssignmentOperator())))), + argumentCountIs(2), hasArgument(1, unless(hasNulloptType()))); +} + +auto isOptionalNulloptAssignment() { + return cxxOperatorCallExpr(hasOverloadedOperatorName("="), + callee(cxxMethodDecl(ofClass(optionalClass()))), + argumentCountIs(2), + hasArgument(1, hasNulloptType())); +} + +auto isStdSwapCall() { + return callExpr(callee(functionDecl(hasName("std::swap"))), + argumentCountIs(2), hasArgument(0, hasOptionalType()), + hasArgument(1, hasOptionalType())); +} + +constexpr llvm::StringLiteral ValueOrCallID = "ValueOrCall"; + +auto isValueOrStringEmptyCall() { + // `opt.value_or("").empty()` + return cxxMemberCallExpr( + callee(cxxMethodDecl(hasName("empty"))), + onImplicitObjectArgument(ignoringImplicit( + cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))), + callee(cxxMethodDecl(hasName("value_or"), + ofClass(optionalClass()))), + hasArgument(0, stringLiteral(hasSize(0)))) + .bind(ValueOrCallID)))); +} + +auto isValueOrNotEqX() { + auto ComparesToSame = [](ast_matchers::internal::Matcher<Stmt> Arg) { + return hasOperands( + ignoringImplicit( + cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))), + callee(cxxMethodDecl(hasName("value_or"), + ofClass(optionalClass()))), + hasArgument(0, Arg)) + .bind(ValueOrCallID)), + ignoringImplicit(Arg)); + }; + + // `opt.value_or(X) != X`, for X is `nullptr`, `""`, or `0`. Ideally, we'd + // support this pattern for any expression, but the AST does not have a + // generic expression comparison facility, so we specialize to common cases + // seen in practice. FIXME: define a matcher that compares values across + // nodes, which would let us generalize this to any `X`. + return binaryOperation(hasOperatorName("!="), + anyOf(ComparesToSame(cxxNullPtrLiteralExpr()), + ComparesToSame(stringLiteral(hasSize(0))), + ComparesToSame(integerLiteral(equals(0))))); +} + +auto isCallReturningOptional() { + return callExpr(hasType(qualType(anyOf( + optionalOrAliasType(), referenceType(pointee(optionalOrAliasType())))))); +} + +/// Sets `HasValueVal` as the symbolic value that represents the "has_value" +/// property of the optional value `OptionalVal`. +void setHasValue(Value &OptionalVal, BoolValue &HasValueVal) { + OptionalVal.setProperty("has_value", HasValueVal); +} + +/// Creates a symbolic value for an `optional` value using `HasValueVal` as the +/// symbolic value of its "has_value" property. +StructValue &createOptionalValue(Environment &Env, BoolValue &HasValueVal) { + auto OptionalVal = std::make_unique<StructValue>(); + setHasValue(*OptionalVal, HasValueVal); + return Env.takeOwnership(std::move(OptionalVal)); +} + +/// Returns the symbolic value that represents the "has_value" property of the +/// optional value `OptionalVal`. Returns null if `OptionalVal` is null. +BoolValue *getHasValue(Environment &Env, Value *OptionalVal) { + if (OptionalVal != nullptr) { + auto *HasValueVal = + cast_or_null<BoolValue>(OptionalVal->getProperty("has_value")); + if (HasValueVal == nullptr) { + HasValueVal = &Env.makeAtomicBoolValue(); + OptionalVal->setProperty("has_value", *HasValueVal); + } + return HasValueVal; + } + return nullptr; +} + +/// If `Type` is a reference type, returns the type of its pointee. Otherwise, +/// returns `Type` itself. +QualType stripReference(QualType Type) { + return Type->isReferenceType() ? Type->getPointeeType() : Type; +} + +/// Returns true if and only if `Type` is an optional type. +bool IsOptionalType(QualType Type) { + if (!Type->isRecordType()) + return false; + // FIXME: Optimize this by avoiding the `getQualifiedNameAsString` call. + auto TypeName = Type->getAsCXXRecordDecl()->getQualifiedNameAsString(); + return TypeName == "std::optional" || TypeName == "absl::optional" || + TypeName == "base::Optional"; +} + +/// Returns the number of optional wrappers in `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)) + return 0; + return 1 + countOptionalWrappers( + ASTCtx, + cast<ClassTemplateSpecializationDecl>(Type->getAsRecordDecl()) + ->getTemplateArgs() + .get(0) + .getAsType() + .getDesugaredType(ASTCtx)); +} + +/// Tries to initialize the `optional`'s value (that is, contents), and return +/// its location. Returns nullptr if the value can't be represented. +StorageLocation *maybeInitializeOptionalValueMember(QualType Q, + Value &OptionalVal, + Environment &Env) { + // The "value" property represents a synthetic field. As such, it needs + // `StorageLocation`, like normal fields (and other variables). So, we model + // it with a `ReferenceValue`, since that includes a storage location. Once + // the property is set, it will be shared by all environments that access the + // `Value` representing the optional (here, `OptionalVal`). + if (auto *ValueProp = OptionalVal.getProperty("value")) { + auto *ValueRef = clang::cast<ReferenceValue>(ValueProp); + auto &ValueLoc = ValueRef->getReferentLoc(); + if (Env.getValue(ValueLoc) == nullptr) { + // The property was previously set, but the value has been lost. This can + // happen, for example, because of an environment merge (where the two + // environments mapped the property to different values, which resulted in + // them both being discarded), or when two blocks in the CFG, with neither + // a dominator of the other, visit the same optional value, or even when a + // block is revisited during testing to collect per-statement state. + // FIXME: This situation means that the optional contents are not shared + // between branches and the like. Practically, this lack of sharing + // reduces the precision of the model when the contents are relevant to + // the check, like another optional or a boolean that influences control + // flow. + auto *ValueVal = Env.createValue(ValueLoc.getType()); + if (ValueVal == nullptr) + return nullptr; + Env.setValue(ValueLoc, *ValueVal); + } + return &ValueLoc; + } + + auto Ty = stripReference(Q); + auto *ValueVal = Env.createValue(Ty); + if (ValueVal == nullptr) + return nullptr; + auto &ValueLoc = Env.createStorageLocation(Ty); + Env.setValue(ValueLoc, *ValueVal); + auto ValueRef = std::make_unique<ReferenceValue>(ValueLoc); + OptionalVal.setProperty("value", Env.takeOwnership(std::move(ValueRef))); + return &ValueLoc; +} + +void initializeOptionalReference(const Expr *OptionalExpr, + const MatchFinder::MatchResult &, + LatticeTransferState &State) { + if (auto *OptionalVal = + State.Env.getValue(*OptionalExpr, SkipPast::Reference)) { + if (OptionalVal->getProperty("has_value") == nullptr) { + setHasValue(*OptionalVal, State.Env.makeAtomicBoolValue()); + } + } +} + +/// Returns true if and only if `OptionalVal` is initialized and known to be +/// empty in `Env. +bool isEmptyOptional(const Value &OptionalVal, const Environment &Env) { + auto *HasValueVal = + cast_or_null<BoolValue>(OptionalVal.getProperty("has_value")); + return HasValueVal != nullptr && + Env.flowConditionImplies(Env.makeNot(*HasValueVal)); +} + +/// Returns true if and only if `OptionalVal` is initialized and known to be +/// non-empty in `Env. +bool isNonEmptyOptional(const Value &OptionalVal, const Environment &Env) { + auto *HasValueVal = + cast_or_null<BoolValue>(OptionalVal.getProperty("has_value")); + return HasValueVal != nullptr && Env.flowConditionImplies(*HasValueVal); +} + +void transferUnwrapCall(const Expr *UnwrapExpr, const Expr *ObjectExpr, + LatticeTransferState &State) { + if (auto *OptionalVal = + State.Env.getValue(*ObjectExpr, SkipPast::ReferenceThenPointer)) { + if (State.Env.getStorageLocation(*UnwrapExpr, SkipPast::None) == nullptr) + if (auto *Loc = maybeInitializeOptionalValueMember( + UnwrapExpr->getType(), *OptionalVal, State.Env)) + State.Env.setStorageLocation(*UnwrapExpr, *Loc); + } +} + +void transferMakeOptionalCall(const CallExpr *E, + const MatchFinder::MatchResult &, + LatticeTransferState &State) { + auto &Loc = State.Env.createStorageLocation(*E); + State.Env.setStorageLocation(*E, Loc); + State.Env.setValue( + Loc, createOptionalValue(State.Env, State.Env.getBoolLiteralValue(true))); +} + +void transferOptionalHasValueCall(const CXXMemberCallExpr *CallExpr, + const MatchFinder::MatchResult &, + LatticeTransferState &State) { + if (auto *HasValueVal = getHasValue( + State.Env, State.Env.getValue(*CallExpr->getImplicitObjectArgument(), + SkipPast::ReferenceThenPointer))) { + auto &CallExprLoc = State.Env.createStorageLocation(*CallExpr); + State.Env.setValue(CallExprLoc, *HasValueVal); + State.Env.setStorageLocation(*CallExpr, CallExprLoc); + } +} + +/// `ModelPred` builds a logical formula relating the predicate in +/// `ValueOrPredExpr` to the optional's `has_value` property. +void transferValueOrImpl(const clang::Expr *ValueOrPredExpr, + const MatchFinder::MatchResult &Result, + LatticeTransferState &State, + BoolValue &(*ModelPred)(Environment &Env, + BoolValue &ExprVal, + BoolValue &HasValueVal)) { + auto &Env = State.Env; + + const auto *ObjectArgumentExpr = + Result.Nodes.getNodeAs<clang::CXXMemberCallExpr>(ValueOrCallID) + ->getImplicitObjectArgument(); + + auto *HasValueVal = getHasValue( + State.Env, + State.Env.getValue(*ObjectArgumentExpr, SkipPast::ReferenceThenPointer)); + 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)); +} + +void transferValueOrStringEmptyCall(const clang::Expr *ComparisonExpr, + const MatchFinder::MatchResult &Result, + LatticeTransferState &State) { + return transferValueOrImpl(ComparisonExpr, Result, State, + [](Environment &Env, BoolValue &ExprVal, + BoolValue &HasValueVal) -> BoolValue & { + // If the result is *not* empty, then we know the + // optional must have been holding a value. If + // `ExprVal` is true, though, we don't learn + // anything definite about `has_value`, so we + // don't add any corresponding implications to + // the flow condition. + return Env.makeImplication(Env.makeNot(ExprVal), + HasValueVal); + }); +} + +void transferValueOrNotEqX(const Expr *ComparisonExpr, + const MatchFinder::MatchResult &Result, + LatticeTransferState &State) { + transferValueOrImpl(ComparisonExpr, Result, State, + [](Environment &Env, BoolValue &ExprVal, + BoolValue &HasValueVal) -> BoolValue & { + // We know that if `(opt.value_or(X) != X)` then + // `opt.hasValue()`, even without knowing further + // details about the contents of `opt`. + return Env.makeImplication(ExprVal, HasValueVal); + }); +} + +void transferCallReturningOptional(const CallExpr *E, + const MatchFinder::MatchResult &Result, + LatticeTransferState &State) { + if (State.Env.getStorageLocation(*E, SkipPast::None) != nullptr) + return; + + auto &Loc = State.Env.createStorageLocation(*E); + State.Env.setStorageLocation(*E, Loc); + State.Env.setValue( + Loc, createOptionalValue(State.Env, State.Env.makeAtomicBoolValue())); +} + +void assignOptionalValue(const Expr &E, LatticeTransferState &State, + BoolValue &HasValueVal) { + if (auto *OptionalLoc = + State.Env.getStorageLocation(E, SkipPast::ReferenceThenPointer)) { + State.Env.setValue(*OptionalLoc, + createOptionalValue(State.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) { + assert(F.getTemplateSpecializationArgs()->size() > 0); + + const int TemplateParamOptionalWrappersCount = countOptionalWrappers( + *MatchRes.Context, + stripReference(F.getTemplateSpecializationArgs()->get(0).getAsType())); + const int ArgTypeOptionalWrappersCount = + countOptionalWrappers(*MatchRes.Context, stripReference(E.getType())); + + // Check if this is a constructor/assignment call for `optional<T>` with + // argument of type `U` such that `T` is constructible from `U`. + if (TemplateParamOptionalWrappersCount == ArgTypeOptionalWrappersCount) + return State.Env.getBoolLiteralValue(true); + + // This is a constructor/assignment call for `optional<T>` with argument of + // type `optional<U>` such that `T` is constructible from `U`. + if (auto *HasValueVal = + getHasValue(State.Env, State.Env.getValue(E, SkipPast::Reference))) + return *HasValueVal; + return State.Env.makeAtomicBoolValue(); +} + +void transferValueOrConversionConstructor( + const CXXConstructExpr *E, const MatchFinder::MatchResult &MatchRes, + LatticeTransferState &State) { + assert(E->getNumArgs() > 0); + + assignOptionalValue(*E, State, + value_orConversionHasValue(*E->getConstructor(), + *E->getArg(0), MatchRes, + State)); +} + +void transferAssignment(const CXXOperatorCallExpr *E, BoolValue &HasValueVal, + LatticeTransferState &State) { + assert(E->getNumArgs() > 0); + + auto *OptionalLoc = + State.Env.getStorageLocation(*E->getArg(0), SkipPast::Reference); + if (OptionalLoc == nullptr) + return; + + State.Env.setValue(*OptionalLoc, createOptionalValue(State.Env, HasValueVal)); + + // Assign a storage location for the whole expression. + State.Env.setStorageLocation(*E, *OptionalLoc); +} + +void transferValueOrConversionAssignment( + const CXXOperatorCallExpr *E, const MatchFinder::MatchResult &MatchRes, + LatticeTransferState &State) { + assert(E->getNumArgs() > 1); + transferAssignment(E, + value_orConversionHasValue(*E->getDirectCallee(), + *E->getArg(1), MatchRes, State), + State); +} + +void transferNulloptAssignment(const CXXOperatorCallExpr *E, + const MatchFinder::MatchResult &, + LatticeTransferState &State) { + transferAssignment(E, State.Env.getBoolLiteralValue(false), State); +} + +void transferSwap(const StorageLocation &OptionalLoc1, + const StorageLocation &OptionalLoc2, + LatticeTransferState &State) { + auto *OptionalVal1 = State.Env.getValue(OptionalLoc1); + assert(OptionalVal1 != nullptr); + + auto *OptionalVal2 = State.Env.getValue(OptionalLoc2); + assert(OptionalVal2 != nullptr); + + State.Env.setValue(OptionalLoc1, *OptionalVal2); + State.Env.setValue(OptionalLoc2, *OptionalVal1); +} + +void transferSwapCall(const CXXMemberCallExpr *E, + const MatchFinder::MatchResult &, + LatticeTransferState &State) { + assert(E->getNumArgs() == 1); + + auto *OptionalLoc1 = State.Env.getStorageLocation( + *E->getImplicitObjectArgument(), SkipPast::ReferenceThenPointer); + assert(OptionalLoc1 != nullptr); + + auto *OptionalLoc2 = + State.Env.getStorageLocation(*E->getArg(0), SkipPast::Reference); + assert(OptionalLoc2 != nullptr); + + transferSwap(*OptionalLoc1, *OptionalLoc2, State); +} + +void transferStdSwapCall(const CallExpr *E, const MatchFinder::MatchResult &, + LatticeTransferState &State) { + assert(E->getNumArgs() == 2); + + auto *OptionalLoc1 = + State.Env.getStorageLocation(*E->getArg(0), SkipPast::Reference); + assert(OptionalLoc1 != nullptr); + + auto *OptionalLoc2 = + State.Env.getStorageLocation(*E->getArg(1), SkipPast::Reference); + assert(OptionalLoc2 != nullptr); + + transferSwap(*OptionalLoc1, *OptionalLoc2, State); +} + +llvm::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; +} + +StatementMatcher +valueCall(llvm::Optional<StatementMatcher> &IgnorableOptional) { + return isOptionalMemberCallWithName("value", IgnorableOptional); +} + +StatementMatcher +valueOperatorCall(llvm::Optional<StatementMatcher> &IgnorableOptional) { + return expr(anyOf(isOptionalOperatorCallWithName("*", IgnorableOptional), + isOptionalOperatorCallWithName("->", IgnorableOptional))); +} + +auto buildTransferMatchSwitch( + const UncheckedOptionalAccessModelOptions &Options) { + // 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>() + // Attach a symbolic "has_value" state to optional values that we see for + // the first time. + .CaseOf<Expr>( + expr(anyOf(declRefExpr(), memberExpr()), hasOptionalType()), + initializeOptionalReference) + + // make_optional + .CaseOf<CallExpr>(isMakeOptionalCall(), transferMakeOptionalCall) + + // optional::optional + .CaseOf<CXXConstructExpr>( + isOptionalInPlaceConstructor(), + [](const CXXConstructExpr *E, const MatchFinder::MatchResult &, + LatticeTransferState &State) { + assignOptionalValue(*E, State, State.Env.getBoolLiteralValue(true)); + }) + .CaseOf<CXXConstructExpr>( + isOptionalNulloptConstructor(), + [](const CXXConstructExpr *E, const MatchFinder::MatchResult &, + LatticeTransferState &State) { + assignOptionalValue(*E, State, + State.Env.getBoolLiteralValue(false)); + }) + .CaseOf<CXXConstructExpr>(isOptionalValueOrConversionConstructor(), + transferValueOrConversionConstructor) + + // optional::operator= + .CaseOf<CXXOperatorCallExpr>(isOptionalValueOrConversionAssignment(), + transferValueOrConversionAssignment) + .CaseOf<CXXOperatorCallExpr>(isOptionalNulloptAssignment(), + transferNulloptAssignment) + + // optional::value + .CaseOf<CXXMemberCallExpr>( + valueCall(IgnorableOptional), + [](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); + }) + + // optional::has_value + .CaseOf<CXXMemberCallExpr>(isOptionalMemberCallWithName("has_value"), + transferOptionalHasValueCall) + + // optional::operator bool + .CaseOf<CXXMemberCallExpr>(isOptionalMemberCallWithName("operator bool"), + transferOptionalHasValueCall) + + // optional::emplace + .CaseOf<CXXMemberCallExpr>( + isOptionalMemberCallWithName("emplace"), + [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, + LatticeTransferState &State) { + assignOptionalValue(*E->getImplicitObjectArgument(), State, + State.Env.getBoolLiteralValue(true)); + }) + + // optional::reset + .CaseOf<CXXMemberCallExpr>( + isOptionalMemberCallWithName("reset"), + [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, + LatticeTransferState &State) { + assignOptionalValue(*E->getImplicitObjectArgument(), State, + State.Env.getBoolLiteralValue(false)); + }) + + // optional::swap + .CaseOf<CXXMemberCallExpr>(isOptionalMemberCallWithName("swap"), + transferSwapCall) + + // std::swap + .CaseOf<CallExpr>(isStdSwapCall(), transferStdSwapCall) + + // opt.value_or("").empty() + .CaseOf<Expr>(isValueOrStringEmptyCall(), transferValueOrStringEmptyCall) + + // opt.value_or(X) != X + .CaseOf<Expr>(isValueOrNotEqX(), transferValueOrNotEqX) + + // returns optional + .CaseOf<CallExpr>(isCallReturningOptional(), + transferCallReturningOptional) + + .Build(); +} + +std::vector<SourceLocation> diagnoseUnwrapCall(const Expr *UnwrapExpr, + const Expr *ObjectExpr, + const Environment &Env) { + if (auto *OptionalVal = + Env.getValue(*ObjectExpr, SkipPast::ReferenceThenPointer)) { + auto *Prop = OptionalVal->getProperty("has_value"); + if (auto *HasValueVal = cast_or_null<BoolValue>(Prop)) { + if (Env.flowConditionImplies(*HasValueVal)) + return {}; + } + } + + // Record that this unwrap is *not* provably safe. + // FIXME: include either the name of the optional (if applicable) or a source + // range of the access for easier interpretation of the result. + return {ObjectExpr->getBeginLoc()}; +} + +auto buildDiagnoseMatchSwitch( + const UncheckedOptionalAccessModelOptions &Options) { + // 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<const Environment, std::vector<SourceLocation>>() + // optional::value + .CaseOf<CXXMemberCallExpr>( + valueCall(IgnorableOptional), + [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, + const Environment &Env) { + return diagnoseUnwrapCall(E, E->getImplicitObjectArgument(), Env); + }) + + // optional::operator*, optional::operator-> + .CaseOf<CallExpr>( + valueOperatorCall(IgnorableOptional), + [](const CallExpr *E, const MatchFinder::MatchResult &, + const Environment &Env) { + return diagnoseUnwrapCall(E, E->getArg(0), Env); + }) + .Build(); +} + +} // namespace + +ast_matchers::DeclarationMatcher +UncheckedOptionalAccessModel::optionalClassDecl() { + return optionalClass(); +} + +UncheckedOptionalAccessModel::UncheckedOptionalAccessModel( + ASTContext &Ctx, UncheckedOptionalAccessModelOptions Options) + : DataflowAnalysis<UncheckedOptionalAccessModel, NoopLattice>(Ctx), + TransferMatchSwitch(buildTransferMatchSwitch(Options)) {} + +void UncheckedOptionalAccessModel::transfer(const Stmt *S, 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); +} + +bool UncheckedOptionalAccessModel::merge(QualType Type, const Value &Val1, + const Environment &Env1, + const Value &Val2, + const Environment &Env2, + Value &MergedVal, + Environment &MergedEnv) { + if (!IsOptionalType(Type)) + return true; + + auto &HasValueVal = MergedEnv.makeAtomicBoolValue(); + if (isNonEmptyOptional(Val1, Env1) && isNonEmptyOptional(Val2, Env2)) + MergedEnv.addToFlowCondition(HasValueVal); + else if (isEmptyOptional(Val1, Env1) && isEmptyOptional(Val2, Env2)) + MergedEnv.addToFlowCondition(MergedEnv.makeNot(HasValueVal)); + setHasValue(MergedVal, HasValueVal); + return true; +} + +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); +} + +} // namespace dataflow +} // namespace clang |
