Path: blob/main/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
35323 views
//===-- UncheckedOptionalAccessModel.cpp ------------------------*- C++ -*-===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This file defines a dataflow analysis that detects unsafe uses of optional9// values.10//11//===----------------------------------------------------------------------===//1213#include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h"14#include "clang/AST/ASTContext.h"15#include "clang/AST/DeclCXX.h"16#include "clang/AST/Expr.h"17#include "clang/AST/ExprCXX.h"18#include "clang/AST/Stmt.h"19#include "clang/ASTMatchers/ASTMatchers.h"20#include "clang/ASTMatchers/ASTMatchersMacros.h"21#include "clang/Analysis/CFG.h"22#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h"23#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"24#include "clang/Analysis/FlowSensitive/Formula.h"25#include "clang/Analysis/FlowSensitive/NoopLattice.h"26#include "clang/Analysis/FlowSensitive/StorageLocation.h"27#include "clang/Analysis/FlowSensitive/Value.h"28#include "clang/Basic/SourceLocation.h"29#include "llvm/ADT/StringRef.h"30#include "llvm/Support/Casting.h"31#include "llvm/Support/ErrorHandling.h"32#include <cassert>33#include <memory>34#include <optional>35#include <utility>3637namespace clang {38namespace dataflow {3940static bool isTopLevelNamespaceWithName(const NamespaceDecl &NS,41llvm::StringRef Name) {42return NS.getDeclName().isIdentifier() && NS.getName() == Name &&43NS.getParent() != nullptr && NS.getParent()->isTranslationUnit();44}4546static bool hasOptionalClassName(const CXXRecordDecl &RD) {47if (!RD.getDeclName().isIdentifier())48return false;4950if (RD.getName() == "optional") {51if (const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext()))52return N->isStdNamespace() || isTopLevelNamespaceWithName(*N, "absl");53return false;54}5556if (RD.getName() == "Optional") {57// Check whether namespace is "::base" or "::folly".58const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());59return N != nullptr && (isTopLevelNamespaceWithName(*N, "base") ||60isTopLevelNamespaceWithName(*N, "folly"));61}6263return false;64}6566static const CXXRecordDecl *getOptionalBaseClass(const CXXRecordDecl *RD) {67if (RD == nullptr)68return nullptr;69if (hasOptionalClassName(*RD))70return RD;7172if (!RD->hasDefinition())73return nullptr;7475for (const CXXBaseSpecifier &Base : RD->bases())76if (const CXXRecordDecl *BaseClass =77getOptionalBaseClass(Base.getType()->getAsCXXRecordDecl()))78return BaseClass;7980return nullptr;81}8283namespace {8485using namespace ::clang::ast_matchers;86using LatticeTransferState = TransferState<NoopLattice>;8788AST_MATCHER(CXXRecordDecl, optionalClass) { return hasOptionalClassName(Node); }8990AST_MATCHER(CXXRecordDecl, optionalOrDerivedClass) {91return getOptionalBaseClass(&Node) != nullptr;92}9394auto desugarsToOptionalType() {95return hasUnqualifiedDesugaredType(96recordType(hasDeclaration(cxxRecordDecl(optionalClass()))));97}9899auto desugarsToOptionalOrDerivedType() {100return hasUnqualifiedDesugaredType(101recordType(hasDeclaration(cxxRecordDecl(optionalOrDerivedClass()))));102}103104auto hasOptionalType() { return hasType(desugarsToOptionalType()); }105106/// Matches any of the spellings of the optional types and sugar, aliases,107/// derived classes, etc.108auto hasOptionalOrDerivedType() {109return hasType(desugarsToOptionalOrDerivedType());110}111112QualType getPublicType(const Expr *E) {113auto *Cast = dyn_cast<ImplicitCastExpr>(E->IgnoreParens());114if (Cast == nullptr || Cast->getCastKind() != CK_UncheckedDerivedToBase) {115QualType Ty = E->getType();116if (Ty->isPointerType())117return Ty->getPointeeType();118return Ty;119}120121// Is the derived type that we're casting from the type of `*this`? In this122// special case, we can upcast to the base class even if the base is123// non-public.124bool CastingFromThis = isa<CXXThisExpr>(Cast->getSubExpr());125126// Find the least-derived type in the path (i.e. the last entry in the list)127// that we can access.128const CXXBaseSpecifier *PublicBase = nullptr;129for (const CXXBaseSpecifier *Base : Cast->path()) {130if (Base->getAccessSpecifier() != AS_public && !CastingFromThis)131break;132PublicBase = Base;133CastingFromThis = false;134}135136if (PublicBase != nullptr)137return PublicBase->getType();138139// We didn't find any public type that we could cast to. There may be more140// casts in `getSubExpr()`, so recurse. (If there aren't any more casts, this141// will return the type of `getSubExpr()`.)142return getPublicType(Cast->getSubExpr());143}144145// Returns the least-derived type for the receiver of `MCE` that146// `MCE.getImplicitObjectArgument()->IgnoreParentImpCasts()` can be downcast to.147// Effectively, we upcast until we reach a non-public base class, unless that148// base is a base of `*this`.149//150// This is needed to correctly match methods called on types derived from151// `std::optional`.152//153// Say we have a `struct Derived : public std::optional<int> {} d;` For a call154// `d.has_value()`, the `getImplicitObjectArgument()` looks like this:155//156// ImplicitCastExpr 'const std::__optional_storage_base<int>' lvalue157// | <UncheckedDerivedToBase (optional -> __optional_storage_base)>158// `-DeclRefExpr 'Derived' lvalue Var 'd' 'Derived'159//160// The type of the implicit object argument is `__optional_storage_base`161// (since this is the internal type that `has_value()` is declared on). If we162// call `IgnoreParenImpCasts()` on the implicit object argument, we get the163// `DeclRefExpr`, which has type `Derived`. Neither of these types is164// `optional`, and hence neither is sufficient for querying whether we are165// calling a method on `optional`.166//167// Instead, starting with the most derived type, we need to follow the chain of168// casts169QualType getPublicReceiverType(const CXXMemberCallExpr &MCE) {170return getPublicType(MCE.getImplicitObjectArgument());171}172173AST_MATCHER_P(CXXMemberCallExpr, publicReceiverType,174ast_matchers::internal::Matcher<QualType>, InnerMatcher) {175return InnerMatcher.matches(getPublicReceiverType(Node), Finder, Builder);176}177178auto isOptionalMemberCallWithNameMatcher(179ast_matchers::internal::Matcher<NamedDecl> matcher,180const std::optional<StatementMatcher> &Ignorable = std::nullopt) {181return cxxMemberCallExpr(Ignorable ? on(expr(unless(*Ignorable)))182: anything(),183publicReceiverType(desugarsToOptionalType()),184callee(cxxMethodDecl(matcher)));185}186187auto isOptionalOperatorCallWithName(188llvm::StringRef operator_name,189const std::optional<StatementMatcher> &Ignorable = std::nullopt) {190return cxxOperatorCallExpr(191hasOverloadedOperatorName(operator_name),192callee(cxxMethodDecl(ofClass(optionalClass()))),193Ignorable ? callExpr(unless(hasArgument(0, *Ignorable))) : callExpr());194}195196auto isMakeOptionalCall() {197return callExpr(callee(functionDecl(hasAnyName(198"std::make_optional", "base::make_optional",199"absl::make_optional", "folly::make_optional"))),200hasOptionalType());201}202203auto nulloptTypeDecl() {204return namedDecl(hasAnyName("std::nullopt_t", "absl::nullopt_t",205"base::nullopt_t", "folly::None"));206}207208auto hasNulloptType() { return hasType(nulloptTypeDecl()); }209210auto inPlaceClass() {211return recordDecl(hasAnyName("std::in_place_t", "absl::in_place_t",212"base::in_place_t", "folly::in_place_t"));213}214215auto isOptionalNulloptConstructor() {216return cxxConstructExpr(217hasDeclaration(cxxConstructorDecl(parameterCountIs(1),218hasParameter(0, hasNulloptType()))),219hasOptionalOrDerivedType());220}221222auto isOptionalInPlaceConstructor() {223return cxxConstructExpr(hasArgument(0, hasType(inPlaceClass())),224hasOptionalOrDerivedType());225}226227auto isOptionalValueOrConversionConstructor() {228return cxxConstructExpr(229unless(hasDeclaration(230cxxConstructorDecl(anyOf(isCopyConstructor(), isMoveConstructor())))),231argumentCountIs(1), hasArgument(0, unless(hasNulloptType())),232hasOptionalOrDerivedType());233}234235auto isOptionalValueOrConversionAssignment() {236return cxxOperatorCallExpr(237hasOverloadedOperatorName("="),238callee(cxxMethodDecl(ofClass(optionalOrDerivedClass()))),239unless(hasDeclaration(cxxMethodDecl(240anyOf(isCopyAssignmentOperator(), isMoveAssignmentOperator())))),241argumentCountIs(2), hasArgument(1, unless(hasNulloptType())));242}243244auto isOptionalNulloptAssignment() {245return cxxOperatorCallExpr(246hasOverloadedOperatorName("="),247callee(cxxMethodDecl(ofClass(optionalOrDerivedClass()))),248argumentCountIs(2), hasArgument(1, hasNulloptType()));249}250251auto isStdSwapCall() {252return callExpr(callee(functionDecl(hasName("std::swap"))),253argumentCountIs(2),254hasArgument(0, hasOptionalOrDerivedType()),255hasArgument(1, hasOptionalOrDerivedType()));256}257258auto isStdForwardCall() {259return callExpr(callee(functionDecl(hasName("std::forward"))),260argumentCountIs(1),261hasArgument(0, hasOptionalOrDerivedType()));262}263264constexpr llvm::StringLiteral ValueOrCallID = "ValueOrCall";265266auto isValueOrStringEmptyCall() {267// `opt.value_or("").empty()`268return cxxMemberCallExpr(269callee(cxxMethodDecl(hasName("empty"))),270onImplicitObjectArgument(ignoringImplicit(271cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))),272callee(cxxMethodDecl(hasName("value_or"),273ofClass(optionalClass()))),274hasArgument(0, stringLiteral(hasSize(0))))275.bind(ValueOrCallID))));276}277278auto isValueOrNotEqX() {279auto ComparesToSame = [](ast_matchers::internal::Matcher<Stmt> Arg) {280return hasOperands(281ignoringImplicit(282cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))),283callee(cxxMethodDecl(hasName("value_or"),284ofClass(optionalClass()))),285hasArgument(0, Arg))286.bind(ValueOrCallID)),287ignoringImplicit(Arg));288};289290// `opt.value_or(X) != X`, for X is `nullptr`, `""`, or `0`. Ideally, we'd291// support this pattern for any expression, but the AST does not have a292// generic expression comparison facility, so we specialize to common cases293// seen in practice. FIXME: define a matcher that compares values across294// nodes, which would let us generalize this to any `X`.295return binaryOperation(hasOperatorName("!="),296anyOf(ComparesToSame(cxxNullPtrLiteralExpr()),297ComparesToSame(stringLiteral(hasSize(0))),298ComparesToSame(integerLiteral(equals(0)))));299}300301auto isCallReturningOptional() {302return callExpr(hasType(qualType(303anyOf(desugarsToOptionalOrDerivedType(),304referenceType(pointee(desugarsToOptionalOrDerivedType()))))));305}306307template <typename L, typename R>308auto isComparisonOperatorCall(L lhs_arg_matcher, R rhs_arg_matcher) {309return cxxOperatorCallExpr(310anyOf(hasOverloadedOperatorName("=="), hasOverloadedOperatorName("!=")),311argumentCountIs(2), hasArgument(0, lhs_arg_matcher),312hasArgument(1, rhs_arg_matcher));313}314315/// Ensures that `Expr` is mapped to a `BoolValue` and returns its formula.316const Formula &forceBoolValue(Environment &Env, const Expr &Expr) {317auto *Value = Env.get<BoolValue>(Expr);318if (Value != nullptr)319return Value->formula();320321Value = &Env.makeAtomicBoolValue();322Env.setValue(Expr, *Value);323return Value->formula();324}325326StorageLocation &locForHasValue(const RecordStorageLocation &OptionalLoc) {327return OptionalLoc.getSyntheticField("has_value");328}329330StorageLocation &locForValue(const RecordStorageLocation &OptionalLoc) {331return OptionalLoc.getSyntheticField("value");332}333334/// Sets `HasValueVal` as the symbolic value that represents the "has_value"335/// property of the optional at `OptionalLoc`.336void setHasValue(RecordStorageLocation &OptionalLoc, BoolValue &HasValueVal,337Environment &Env) {338Env.setValue(locForHasValue(OptionalLoc), HasValueVal);339}340341/// Returns the symbolic value that represents the "has_value" property of the342/// optional at `OptionalLoc`. Returns null if `OptionalLoc` is null.343BoolValue *getHasValue(Environment &Env, RecordStorageLocation *OptionalLoc) {344if (OptionalLoc == nullptr)345return nullptr;346StorageLocation &HasValueLoc = locForHasValue(*OptionalLoc);347auto *HasValueVal = Env.get<BoolValue>(HasValueLoc);348if (HasValueVal == nullptr) {349HasValueVal = &Env.makeAtomicBoolValue();350Env.setValue(HasValueLoc, *HasValueVal);351}352return HasValueVal;353}354355QualType valueTypeFromOptionalDecl(const CXXRecordDecl &RD) {356auto &CTSD = cast<ClassTemplateSpecializationDecl>(RD);357return CTSD.getTemplateArgs()[0].getAsType();358}359360/// Returns the number of optional wrappers in `Type`.361///362/// For example, if `Type` is `optional<optional<int>>`, the result of this363/// function will be 2.364int countOptionalWrappers(const ASTContext &ASTCtx, QualType Type) {365const CXXRecordDecl *Optional =366getOptionalBaseClass(Type->getAsCXXRecordDecl());367if (Optional == nullptr)368return 0;369return 1 + countOptionalWrappers(370ASTCtx,371valueTypeFromOptionalDecl(*Optional).getDesugaredType(ASTCtx));372}373374StorageLocation *getLocBehindPossiblePointer(const Expr &E,375const Environment &Env) {376if (E.isPRValue()) {377if (auto *PointerVal = dyn_cast_or_null<PointerValue>(Env.getValue(E)))378return &PointerVal->getPointeeLoc();379return nullptr;380}381return Env.getStorageLocation(E);382}383384void transferUnwrapCall(const Expr *UnwrapExpr, const Expr *ObjectExpr,385LatticeTransferState &State) {386if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>(387getLocBehindPossiblePointer(*ObjectExpr, State.Env))) {388if (State.Env.getStorageLocation(*UnwrapExpr) == nullptr)389State.Env.setStorageLocation(*UnwrapExpr, locForValue(*OptionalLoc));390}391}392393void transferArrowOpCall(const Expr *UnwrapExpr, const Expr *ObjectExpr,394LatticeTransferState &State) {395if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>(396getLocBehindPossiblePointer(*ObjectExpr, State.Env)))397State.Env.setValue(398*UnwrapExpr, State.Env.create<PointerValue>(locForValue(*OptionalLoc)));399}400401void transferMakeOptionalCall(const CallExpr *E,402const MatchFinder::MatchResult &,403LatticeTransferState &State) {404setHasValue(State.Env.getResultObjectLocation(*E),405State.Env.getBoolLiteralValue(true), State.Env);406}407408void transferOptionalHasValueCall(const CXXMemberCallExpr *CallExpr,409const MatchFinder::MatchResult &,410LatticeTransferState &State) {411if (auto *HasValueVal = getHasValue(412State.Env, getImplicitObjectLocation(*CallExpr, State.Env))) {413State.Env.setValue(*CallExpr, *HasValueVal);414}415}416417/// `ModelPred` builds a logical formula relating the predicate in418/// `ValueOrPredExpr` to the optional's `has_value` property.419void transferValueOrImpl(420const clang::Expr *ValueOrPredExpr, const MatchFinder::MatchResult &Result,421LatticeTransferState &State,422const Formula &(*ModelPred)(Environment &Env, const Formula &ExprVal,423const Formula &HasValueVal)) {424auto &Env = State.Env;425426const auto *MCE =427Result.Nodes.getNodeAs<clang::CXXMemberCallExpr>(ValueOrCallID);428429auto *HasValueVal =430getHasValue(State.Env, getImplicitObjectLocation(*MCE, State.Env));431if (HasValueVal == nullptr)432return;433434Env.assume(ModelPred(Env, forceBoolValue(Env, *ValueOrPredExpr),435HasValueVal->formula()));436}437438void transferValueOrStringEmptyCall(const clang::Expr *ComparisonExpr,439const MatchFinder::MatchResult &Result,440LatticeTransferState &State) {441return transferValueOrImpl(ComparisonExpr, Result, State,442[](Environment &Env, const Formula &ExprVal,443const Formula &HasValueVal) -> const Formula & {444auto &A = Env.arena();445// If the result is *not* empty, then we know the446// optional must have been holding a value. If447// `ExprVal` is true, though, we don't learn448// anything definite about `has_value`, so we449// don't add any corresponding implications to450// the flow condition.451return A.makeImplies(A.makeNot(ExprVal),452HasValueVal);453});454}455456void transferValueOrNotEqX(const Expr *ComparisonExpr,457const MatchFinder::MatchResult &Result,458LatticeTransferState &State) {459transferValueOrImpl(ComparisonExpr, Result, State,460[](Environment &Env, const Formula &ExprVal,461const Formula &HasValueVal) -> const Formula & {462auto &A = Env.arena();463// We know that if `(opt.value_or(X) != X)` then464// `opt.hasValue()`, even without knowing further465// details about the contents of `opt`.466return A.makeImplies(ExprVal, HasValueVal);467});468}469470void transferCallReturningOptional(const CallExpr *E,471const MatchFinder::MatchResult &Result,472LatticeTransferState &State) {473RecordStorageLocation *Loc = nullptr;474if (E->isPRValue()) {475Loc = &State.Env.getResultObjectLocation(*E);476} else {477Loc = State.Env.get<RecordStorageLocation>(*E);478if (Loc == nullptr) {479Loc = &cast<RecordStorageLocation>(State.Env.createStorageLocation(*E));480State.Env.setStorageLocation(*E, *Loc);481}482}483484if (State.Env.getValue(locForHasValue(*Loc)) != nullptr)485return;486487setHasValue(*Loc, State.Env.makeAtomicBoolValue(), State.Env);488}489490void constructOptionalValue(const Expr &E, Environment &Env,491BoolValue &HasValueVal) {492RecordStorageLocation &Loc = Env.getResultObjectLocation(E);493setHasValue(Loc, HasValueVal, Env);494}495496/// Returns a symbolic value for the "has_value" property of an `optional<T>`497/// value that is constructed/assigned from a value of type `U` or `optional<U>`498/// where `T` is constructible from `U`.499BoolValue &valueOrConversionHasValue(QualType DestType, const Expr &E,500const MatchFinder::MatchResult &MatchRes,501LatticeTransferState &State) {502const int DestTypeOptionalWrappersCount =503countOptionalWrappers(*MatchRes.Context, DestType);504const int ArgTypeOptionalWrappersCount = countOptionalWrappers(505*MatchRes.Context, E.getType().getNonReferenceType());506507// Is this an constructor of the form `template<class U> optional(U &&)` /508// assignment of the form `template<class U> optional& operator=(U &&)`509// (where `T` is assignable / constructible from `U`)?510// We recognize this because the number of optionals in the optional being511// assigned to is different from the function argument type.512if (DestTypeOptionalWrappersCount != ArgTypeOptionalWrappersCount)513return State.Env.getBoolLiteralValue(true);514515// Otherwise, this must be a constructor of the form516// `template <class U> optional<optional<U> &&)` / assignment of the form517// `template <class U> optional& operator=(optional<U> &&)518// (where, again, `T` is assignable / constructible from `U`).519auto *Loc = State.Env.get<RecordStorageLocation>(E);520if (auto *HasValueVal = getHasValue(State.Env, Loc))521return *HasValueVal;522return State.Env.makeAtomicBoolValue();523}524525void transferValueOrConversionConstructor(526const CXXConstructExpr *E, const MatchFinder::MatchResult &MatchRes,527LatticeTransferState &State) {528assert(E->getNumArgs() > 0);529530constructOptionalValue(531*E, State.Env,532valueOrConversionHasValue(533E->getConstructor()->getThisType()->getPointeeType(), *E->getArg(0),534MatchRes, State));535}536537void transferAssignment(const CXXOperatorCallExpr *E, BoolValue &HasValueVal,538LatticeTransferState &State) {539assert(E->getNumArgs() > 0);540541if (auto *Loc = State.Env.get<RecordStorageLocation>(*E->getArg(0))) {542setHasValue(*Loc, HasValueVal, State.Env);543544// Assign a storage location for the whole expression.545State.Env.setStorageLocation(*E, *Loc);546}547}548549void transferValueOrConversionAssignment(550const CXXOperatorCallExpr *E, const MatchFinder::MatchResult &MatchRes,551LatticeTransferState &State) {552assert(E->getNumArgs() > 1);553transferAssignment(554E,555valueOrConversionHasValue(E->getArg(0)->getType().getNonReferenceType(),556*E->getArg(1), MatchRes, State),557State);558}559560void transferNulloptAssignment(const CXXOperatorCallExpr *E,561const MatchFinder::MatchResult &,562LatticeTransferState &State) {563transferAssignment(E, State.Env.getBoolLiteralValue(false), State);564}565566void transferSwap(RecordStorageLocation *Loc1, RecordStorageLocation *Loc2,567Environment &Env) {568// We account for cases where one or both of the optionals are not modeled,569// either lacking associated storage locations, or lacking values associated570// to such storage locations.571572if (Loc1 == nullptr) {573if (Loc2 != nullptr)574setHasValue(*Loc2, Env.makeAtomicBoolValue(), Env);575return;576}577if (Loc2 == nullptr) {578setHasValue(*Loc1, Env.makeAtomicBoolValue(), Env);579return;580}581582// Both expressions have locations, though they may not have corresponding583// values. In that case, we create a fresh value at this point. Note that if584// two branches both do this, they will not share the value, but it at least585// allows for local reasoning about the value. To avoid the above, we would586// need *lazy* value allocation.587// FIXME: allocate values lazily, instead of just creating a fresh value.588BoolValue *BoolVal1 = getHasValue(Env, Loc1);589if (BoolVal1 == nullptr)590BoolVal1 = &Env.makeAtomicBoolValue();591592BoolValue *BoolVal2 = getHasValue(Env, Loc2);593if (BoolVal2 == nullptr)594BoolVal2 = &Env.makeAtomicBoolValue();595596setHasValue(*Loc1, *BoolVal2, Env);597setHasValue(*Loc2, *BoolVal1, Env);598}599600void transferSwapCall(const CXXMemberCallExpr *E,601const MatchFinder::MatchResult &,602LatticeTransferState &State) {603assert(E->getNumArgs() == 1);604auto *OtherLoc = State.Env.get<RecordStorageLocation>(*E->getArg(0));605transferSwap(getImplicitObjectLocation(*E, State.Env), OtherLoc, State.Env);606}607608void transferStdSwapCall(const CallExpr *E, const MatchFinder::MatchResult &,609LatticeTransferState &State) {610assert(E->getNumArgs() == 2);611auto *Arg0Loc = State.Env.get<RecordStorageLocation>(*E->getArg(0));612auto *Arg1Loc = State.Env.get<RecordStorageLocation>(*E->getArg(1));613transferSwap(Arg0Loc, Arg1Loc, State.Env);614}615616void transferStdForwardCall(const CallExpr *E, const MatchFinder::MatchResult &,617LatticeTransferState &State) {618assert(E->getNumArgs() == 1);619620if (auto *Loc = State.Env.getStorageLocation(*E->getArg(0)))621State.Env.setStorageLocation(*E, *Loc);622}623624const Formula &evaluateEquality(Arena &A, const Formula &EqVal,625const Formula &LHS, const Formula &RHS) {626// Logically, an optional<T> object is composed of two values - a `has_value`627// bit and a value of type T. Equality of optional objects compares both628// values. Therefore, merely comparing the `has_value` bits isn't sufficient:629// when two optional objects are engaged, the equality of their respective630// values of type T matters. Since we only track the `has_value` bits, we631// can't make any conclusions about equality when we know that two optional632// objects are engaged.633//634// We express this as two facts about the equality:635// a) EqVal => (LHS & RHS) v (!RHS & !LHS)636// If they are equal, then either both are set or both are unset.637// b) (!LHS & !RHS) => EqVal638// If neither is set, then they are equal.639// We rewrite b) as !EqVal => (LHS v RHS), for a more compact formula.640return A.makeAnd(641A.makeImplies(EqVal, A.makeOr(A.makeAnd(LHS, RHS),642A.makeAnd(A.makeNot(LHS), A.makeNot(RHS)))),643A.makeImplies(A.makeNot(EqVal), A.makeOr(LHS, RHS)));644}645646void transferOptionalAndOptionalCmp(const clang::CXXOperatorCallExpr *CmpExpr,647const MatchFinder::MatchResult &,648LatticeTransferState &State) {649Environment &Env = State.Env;650auto &A = Env.arena();651auto *CmpValue = &forceBoolValue(Env, *CmpExpr);652auto *Arg0Loc = Env.get<RecordStorageLocation>(*CmpExpr->getArg(0));653if (auto *LHasVal = getHasValue(Env, Arg0Loc)) {654auto *Arg1Loc = Env.get<RecordStorageLocation>(*CmpExpr->getArg(1));655if (auto *RHasVal = getHasValue(Env, Arg1Loc)) {656if (CmpExpr->getOperator() == clang::OO_ExclaimEqual)657CmpValue = &A.makeNot(*CmpValue);658Env.assume(evaluateEquality(A, *CmpValue, LHasVal->formula(),659RHasVal->formula()));660}661}662}663664void transferOptionalAndValueCmp(const clang::CXXOperatorCallExpr *CmpExpr,665const clang::Expr *E, Environment &Env) {666auto &A = Env.arena();667auto *CmpValue = &forceBoolValue(Env, *CmpExpr);668auto *Loc = Env.get<RecordStorageLocation>(*E);669if (auto *HasVal = getHasValue(Env, Loc)) {670if (CmpExpr->getOperator() == clang::OO_ExclaimEqual)671CmpValue = &A.makeNot(*CmpValue);672Env.assume(673evaluateEquality(A, *CmpValue, HasVal->formula(), A.makeLiteral(true)));674}675}676677void transferOptionalAndNulloptCmp(const clang::CXXOperatorCallExpr *CmpExpr,678const clang::Expr *E, Environment &Env) {679auto &A = Env.arena();680auto *CmpValue = &forceBoolValue(Env, *CmpExpr);681auto *Loc = Env.get<RecordStorageLocation>(*E);682if (auto *HasVal = getHasValue(Env, Loc)) {683if (CmpExpr->getOperator() == clang::OO_ExclaimEqual)684CmpValue = &A.makeNot(*CmpValue);685Env.assume(evaluateEquality(A, *CmpValue, HasVal->formula(),686A.makeLiteral(false)));687}688}689690std::optional<StatementMatcher>691ignorableOptional(const UncheckedOptionalAccessModelOptions &Options) {692if (Options.IgnoreSmartPointerDereference) {693auto SmartPtrUse = expr(ignoringParenImpCasts(cxxOperatorCallExpr(694anyOf(hasOverloadedOperatorName("->"), hasOverloadedOperatorName("*")),695unless(hasArgument(0, expr(hasOptionalType()))))));696return expr(697anyOf(SmartPtrUse, memberExpr(hasObjectExpression(SmartPtrUse))));698}699return std::nullopt;700}701702StatementMatcher703valueCall(const std::optional<StatementMatcher> &IgnorableOptional) {704return isOptionalMemberCallWithNameMatcher(hasName("value"),705IgnorableOptional);706}707708StatementMatcher709valueOperatorCall(const std::optional<StatementMatcher> &IgnorableOptional) {710return expr(anyOf(isOptionalOperatorCallWithName("*", IgnorableOptional),711isOptionalOperatorCallWithName("->", IgnorableOptional)));712}713714auto buildTransferMatchSwitch() {715// FIXME: Evaluate the efficiency of matchers. If using matchers results in a716// lot of duplicated work (e.g. string comparisons), consider providing APIs717// that avoid it through memoization.718return CFGMatchSwitchBuilder<LatticeTransferState>()719// make_optional720.CaseOfCFGStmt<CallExpr>(isMakeOptionalCall(), transferMakeOptionalCall)721722// optional::optional (in place)723.CaseOfCFGStmt<CXXConstructExpr>(724isOptionalInPlaceConstructor(),725[](const CXXConstructExpr *E, const MatchFinder::MatchResult &,726LatticeTransferState &State) {727constructOptionalValue(*E, State.Env,728State.Env.getBoolLiteralValue(true));729})730// optional::optional(nullopt_t)731.CaseOfCFGStmt<CXXConstructExpr>(732isOptionalNulloptConstructor(),733[](const CXXConstructExpr *E, const MatchFinder::MatchResult &,734LatticeTransferState &State) {735constructOptionalValue(*E, State.Env,736State.Env.getBoolLiteralValue(false));737})738// optional::optional (value/conversion)739.CaseOfCFGStmt<CXXConstructExpr>(isOptionalValueOrConversionConstructor(),740transferValueOrConversionConstructor)741742// optional::operator=743.CaseOfCFGStmt<CXXOperatorCallExpr>(744isOptionalValueOrConversionAssignment(),745transferValueOrConversionAssignment)746.CaseOfCFGStmt<CXXOperatorCallExpr>(isOptionalNulloptAssignment(),747transferNulloptAssignment)748749// optional::value750.CaseOfCFGStmt<CXXMemberCallExpr>(751valueCall(std::nullopt),752[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,753LatticeTransferState &State) {754transferUnwrapCall(E, E->getImplicitObjectArgument(), State);755})756757// optional::operator*758.CaseOfCFGStmt<CallExpr>(isOptionalOperatorCallWithName("*"),759[](const CallExpr *E,760const MatchFinder::MatchResult &,761LatticeTransferState &State) {762transferUnwrapCall(E, E->getArg(0), State);763})764765// optional::operator->766.CaseOfCFGStmt<CallExpr>(isOptionalOperatorCallWithName("->"),767[](const CallExpr *E,768const MatchFinder::MatchResult &,769LatticeTransferState &State) {770transferArrowOpCall(E, E->getArg(0), State);771})772773// optional::has_value, optional::hasValue774// Of the supported optionals only folly::Optional uses hasValue, but this775// will also pass for other types776.CaseOfCFGStmt<CXXMemberCallExpr>(777isOptionalMemberCallWithNameMatcher(778hasAnyName("has_value", "hasValue")),779transferOptionalHasValueCall)780781// optional::operator bool782.CaseOfCFGStmt<CXXMemberCallExpr>(783isOptionalMemberCallWithNameMatcher(hasName("operator bool")),784transferOptionalHasValueCall)785786// optional::emplace787.CaseOfCFGStmt<CXXMemberCallExpr>(788isOptionalMemberCallWithNameMatcher(hasName("emplace")),789[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,790LatticeTransferState &State) {791if (RecordStorageLocation *Loc =792getImplicitObjectLocation(*E, State.Env)) {793setHasValue(*Loc, State.Env.getBoolLiteralValue(true), State.Env);794}795})796797// optional::reset798.CaseOfCFGStmt<CXXMemberCallExpr>(799isOptionalMemberCallWithNameMatcher(hasName("reset")),800[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,801LatticeTransferState &State) {802if (RecordStorageLocation *Loc =803getImplicitObjectLocation(*E, State.Env)) {804setHasValue(*Loc, State.Env.getBoolLiteralValue(false),805State.Env);806}807})808809// optional::swap810.CaseOfCFGStmt<CXXMemberCallExpr>(811isOptionalMemberCallWithNameMatcher(hasName("swap")),812transferSwapCall)813814// std::swap815.CaseOfCFGStmt<CallExpr>(isStdSwapCall(), transferStdSwapCall)816817// std::forward818.CaseOfCFGStmt<CallExpr>(isStdForwardCall(), transferStdForwardCall)819820// opt.value_or("").empty()821.CaseOfCFGStmt<Expr>(isValueOrStringEmptyCall(),822transferValueOrStringEmptyCall)823824// opt.value_or(X) != X825.CaseOfCFGStmt<Expr>(isValueOrNotEqX(), transferValueOrNotEqX)826827// Comparisons (==, !=):828.CaseOfCFGStmt<CXXOperatorCallExpr>(829isComparisonOperatorCall(hasOptionalType(), hasOptionalType()),830transferOptionalAndOptionalCmp)831.CaseOfCFGStmt<CXXOperatorCallExpr>(832isComparisonOperatorCall(hasOptionalType(), hasNulloptType()),833[](const clang::CXXOperatorCallExpr *Cmp,834const MatchFinder::MatchResult &, LatticeTransferState &State) {835transferOptionalAndNulloptCmp(Cmp, Cmp->getArg(0), State.Env);836})837.CaseOfCFGStmt<CXXOperatorCallExpr>(838isComparisonOperatorCall(hasNulloptType(), hasOptionalType()),839[](const clang::CXXOperatorCallExpr *Cmp,840const MatchFinder::MatchResult &, LatticeTransferState &State) {841transferOptionalAndNulloptCmp(Cmp, Cmp->getArg(1), State.Env);842})843.CaseOfCFGStmt<CXXOperatorCallExpr>(844isComparisonOperatorCall(845hasOptionalType(),846unless(anyOf(hasOptionalType(), hasNulloptType()))),847[](const clang::CXXOperatorCallExpr *Cmp,848const MatchFinder::MatchResult &, LatticeTransferState &State) {849transferOptionalAndValueCmp(Cmp, Cmp->getArg(0), State.Env);850})851.CaseOfCFGStmt<CXXOperatorCallExpr>(852isComparisonOperatorCall(853unless(anyOf(hasOptionalType(), hasNulloptType())),854hasOptionalType()),855[](const clang::CXXOperatorCallExpr *Cmp,856const MatchFinder::MatchResult &, LatticeTransferState &State) {857transferOptionalAndValueCmp(Cmp, Cmp->getArg(1), State.Env);858})859860// returns optional861.CaseOfCFGStmt<CallExpr>(isCallReturningOptional(),862transferCallReturningOptional)863864.Build();865}866867llvm::SmallVector<SourceLocation> diagnoseUnwrapCall(const Expr *ObjectExpr,868const Environment &Env) {869if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>(870getLocBehindPossiblePointer(*ObjectExpr, Env))) {871auto *Prop = Env.getValue(locForHasValue(*OptionalLoc));872if (auto *HasValueVal = cast_or_null<BoolValue>(Prop)) {873if (Env.proves(HasValueVal->formula()))874return {};875}876}877878// Record that this unwrap is *not* provably safe.879// FIXME: include either the name of the optional (if applicable) or a source880// range of the access for easier interpretation of the result.881return {ObjectExpr->getBeginLoc()};882}883884auto buildDiagnoseMatchSwitch(885const UncheckedOptionalAccessModelOptions &Options) {886// FIXME: Evaluate the efficiency of matchers. If using matchers results in a887// lot of duplicated work (e.g. string comparisons), consider providing APIs888// that avoid it through memoization.889auto IgnorableOptional = ignorableOptional(Options);890return CFGMatchSwitchBuilder<const Environment,891llvm::SmallVector<SourceLocation>>()892// optional::value893.CaseOfCFGStmt<CXXMemberCallExpr>(894valueCall(IgnorableOptional),895[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,896const Environment &Env) {897return diagnoseUnwrapCall(E->getImplicitObjectArgument(), Env);898})899900// optional::operator*, optional::operator->901.CaseOfCFGStmt<CallExpr>(valueOperatorCall(IgnorableOptional),902[](const CallExpr *E,903const MatchFinder::MatchResult &,904const Environment &Env) {905return diagnoseUnwrapCall(E->getArg(0), Env);906})907.Build();908}909910} // namespace911912ast_matchers::DeclarationMatcher913UncheckedOptionalAccessModel::optionalClassDecl() {914return cxxRecordDecl(optionalClass());915}916917UncheckedOptionalAccessModel::UncheckedOptionalAccessModel(ASTContext &Ctx,918Environment &Env)919: DataflowAnalysis<UncheckedOptionalAccessModel, NoopLattice>(Ctx),920TransferMatchSwitch(buildTransferMatchSwitch()) {921Env.getDataflowAnalysisContext().setSyntheticFieldCallback(922[&Ctx](QualType Ty) -> llvm::StringMap<QualType> {923const CXXRecordDecl *Optional =924getOptionalBaseClass(Ty->getAsCXXRecordDecl());925if (Optional == nullptr)926return {};927return {{"value", valueTypeFromOptionalDecl(*Optional)},928{"has_value", Ctx.BoolTy}};929});930}931932void UncheckedOptionalAccessModel::transfer(const CFGElement &Elt,933NoopLattice &L, Environment &Env) {934LatticeTransferState State(L, Env);935TransferMatchSwitch(Elt, getASTContext(), State);936}937938UncheckedOptionalAccessDiagnoser::UncheckedOptionalAccessDiagnoser(939UncheckedOptionalAccessModelOptions Options)940: DiagnoseMatchSwitch(buildDiagnoseMatchSwitch(Options)) {}941942} // namespace dataflow943} // namespace clang944945946