| //===-- 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 |