//===-- 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 #include #include #include namespace clang { namespace dataflow { namespace { using namespace ::clang::ast_matchers; using LatticeTransferState = TransferState; 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 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 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 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(); 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(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>`, 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(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(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(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(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(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(ValueOrCallID) ->getImplicitObjectArgument(); auto *HasValueVal = getHasValue( State.Env, State.Env.getValue(*ObjectArgumentExpr, SkipPast::ReferenceThenPointer)); if (HasValueVal == nullptr) return; auto *ExprValue = cast_or_null( 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` /// value that is constructed/assigned from a value of type `U` or `optional` /// 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` 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` with argument of // type `optional` 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 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 &IgnorableOptional) { return isOptionalMemberCallWithName("value", IgnorableOptional); } StatementMatcher valueOperatorCall(llvm::Optional &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() // Attach a symbolic "has_value" state to optional values that we see for // the first time. .CaseOf( expr(anyOf(declRefExpr(), memberExpr()), hasOptionalType()), initializeOptionalReference) // make_optional .CaseOf(isMakeOptionalCall(), transferMakeOptionalCall) // optional::optional .CaseOf( isOptionalInPlaceConstructor(), [](const CXXConstructExpr *E, const MatchFinder::MatchResult &, LatticeTransferState &State) { assignOptionalValue(*E, State, State.Env.getBoolLiteralValue(true)); }) .CaseOf( isOptionalNulloptConstructor(), [](const CXXConstructExpr *E, const MatchFinder::MatchResult &, LatticeTransferState &State) { assignOptionalValue(*E, State, State.Env.getBoolLiteralValue(false)); }) .CaseOf(isOptionalValueOrConversionConstructor(), transferValueOrConversionConstructor) // optional::operator= .CaseOf(isOptionalValueOrConversionAssignment(), transferValueOrConversionAssignment) .CaseOf(isOptionalNulloptAssignment(), transferNulloptAssignment) // optional::value .CaseOf( valueCall(IgnorableOptional), [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, LatticeTransferState &State) { transferUnwrapCall(E, E->getImplicitObjectArgument(), State); }) // optional::operator*, optional::operator-> .CaseOf(valueOperatorCall(IgnorableOptional), [](const CallExpr *E, const MatchFinder::MatchResult &, LatticeTransferState &State) { transferUnwrapCall(E, E->getArg(0), State); }) // optional::has_value .CaseOf(isOptionalMemberCallWithName("has_value"), transferOptionalHasValueCall) // optional::operator bool .CaseOf(isOptionalMemberCallWithName("operator bool"), transferOptionalHasValueCall) // optional::emplace .CaseOf( isOptionalMemberCallWithName("emplace"), [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, LatticeTransferState &State) { assignOptionalValue(*E->getImplicitObjectArgument(), State, State.Env.getBoolLiteralValue(true)); }) // optional::reset .CaseOf( isOptionalMemberCallWithName("reset"), [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, LatticeTransferState &State) { assignOptionalValue(*E->getImplicitObjectArgument(), State, State.Env.getBoolLiteralValue(false)); }) // optional::swap .CaseOf(isOptionalMemberCallWithName("swap"), transferSwapCall) // std::swap .CaseOf(isStdSwapCall(), transferStdSwapCall) // opt.value_or("").empty() .CaseOf(isValueOrStringEmptyCall(), transferValueOrStringEmptyCall) // opt.value_or(X) != X .CaseOf(isValueOrNotEqX(), transferValueOrNotEqX) // returns optional .CaseOf(isCallReturningOptional(), transferCallReturningOptional) .Build(); } std::vector 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(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>() // optional::value .CaseOf( valueCall(IgnorableOptional), [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, const Environment &Env) { return diagnoseUnwrapCall(E, E->getImplicitObjectArgument(), Env); }) // optional::operator*, optional::operator-> .CaseOf( 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(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 UncheckedOptionalAccessDiagnoser::diagnose( ASTContext &Context, const Stmt *Stmt, const Environment &Env) { return DiagnoseMatchSwitch(*Stmt, Context, Env); } } // namespace dataflow } // namespace clang