1222 lines · cpp
1//===-- UncheckedOptionalAccessModel.cpp ------------------------*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This file defines a dataflow analysis that detects unsafe uses of optional10// values.11//12//===----------------------------------------------------------------------===//13 14#include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h"15#include "clang/AST/ASTContext.h"16#include "clang/AST/DeclCXX.h"17#include "clang/AST/Expr.h"18#include "clang/AST/ExprCXX.h"19#include "clang/AST/Stmt.h"20#include "clang/AST/Type.h"21#include "clang/ASTMatchers/ASTMatchers.h"22#include "clang/ASTMatchers/ASTMatchersMacros.h"23#include "clang/Analysis/CFG.h"24#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h"25#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"26#include "clang/Analysis/FlowSensitive/Formula.h"27#include "clang/Analysis/FlowSensitive/RecordOps.h"28#include "clang/Analysis/FlowSensitive/SmartPointerAccessorCaching.h"29#include "clang/Analysis/FlowSensitive/StorageLocation.h"30#include "clang/Analysis/FlowSensitive/Value.h"31#include "clang/Basic/OperatorKinds.h"32#include "clang/Basic/SourceLocation.h"33#include "llvm/ADT/StringRef.h"34#include "llvm/Support/ErrorHandling.h"35#include <cassert>36#include <optional>37 38namespace clang {39namespace dataflow {40 41// Note: the Names appear in reverse order. E.g., to check42// if NS is foo::bar::, call isFullyQualifiedNamespaceEqualTo(NS, "bar", "foo")43template <class... NameTypes>44static bool isFullyQualifiedNamespaceEqualTo(const NamespaceDecl &NS,45 llvm::StringRef Name,46 NameTypes... Names) {47 if (!(NS.getDeclName().isIdentifier() && NS.getName() == Name &&48 NS.getParent() != nullptr))49 return false;50 51 if constexpr (sizeof...(NameTypes) > 0) {52 if (NS.getParent()->isTranslationUnit())53 return false;54 if (const auto *NextNS = dyn_cast_or_null<NamespaceDecl>(NS.getParent()))55 return isFullyQualifiedNamespaceEqualTo(*NextNS, Names...);56 return false;57 } else {58 return NS.getParent()->isTranslationUnit();59 }60}61 62static bool hasOptionalClassName(const CXXRecordDecl &RD) {63 if (!RD.getDeclName().isIdentifier())64 return false;65 66 if (RD.getName() == "optional") {67 if (const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext()))68 return N->isStdNamespace() ||69 isFullyQualifiedNamespaceEqualTo(*N, "absl") ||70 isFullyQualifiedNamespaceEqualTo(*N, "bsl");71 return false;72 }73 74 if (RD.getName() == "Optional") {75 // Check whether namespace is "::base" or "::folly".76 const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());77 return N != nullptr && (isFullyQualifiedNamespaceEqualTo(*N, "base") ||78 isFullyQualifiedNamespaceEqualTo(*N, "folly"));79 }80 81 if (RD.getName() == "NullableValue") {82 const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());83 return N != nullptr &&84 isFullyQualifiedNamespaceEqualTo(*N, "bdlb", "BloombergLP");85 }86 87 return false;88}89 90static const CXXRecordDecl *getOptionalBaseClass(const CXXRecordDecl *RD) {91 if (RD == nullptr)92 return nullptr;93 if (hasOptionalClassName(*RD))94 return RD;95 96 if (!RD->hasDefinition())97 return nullptr;98 99 for (const CXXBaseSpecifier &Base : RD->bases())100 if (const CXXRecordDecl *BaseClass =101 getOptionalBaseClass(Base.getType()->getAsCXXRecordDecl()))102 return BaseClass;103 104 return nullptr;105}106 107static bool isSupportedOptionalType(QualType Ty) {108 const CXXRecordDecl *Optional =109 getOptionalBaseClass(Ty->getAsCXXRecordDecl());110 return Optional != nullptr;111}112 113namespace {114 115using namespace ::clang::ast_matchers;116 117using LatticeTransferState = TransferState<UncheckedOptionalAccessLattice>;118 119AST_MATCHER(CXXRecordDecl, optionalClass) { return hasOptionalClassName(Node); }120 121AST_MATCHER(CXXRecordDecl, optionalOrDerivedClass) {122 return getOptionalBaseClass(&Node) != nullptr;123}124 125auto desugarsToOptionalType() {126 return hasUnqualifiedDesugaredType(127 recordType(hasDeclaration(cxxRecordDecl(optionalClass()))));128}129 130auto desugarsToOptionalOrDerivedType() {131 return hasUnqualifiedDesugaredType(132 recordType(hasDeclaration(cxxRecordDecl(optionalOrDerivedClass()))));133}134 135auto hasOptionalType() { return hasType(desugarsToOptionalType()); }136 137/// Matches any of the spellings of the optional types and sugar, aliases,138/// derived classes, etc.139auto hasOptionalOrDerivedType() {140 return hasType(desugarsToOptionalOrDerivedType());141}142 143QualType getPublicType(const Expr *E) {144 auto *Cast = dyn_cast<ImplicitCastExpr>(E->IgnoreParens());145 if (Cast == nullptr || Cast->getCastKind() != CK_UncheckedDerivedToBase) {146 QualType Ty = E->getType();147 if (Ty->isPointerType())148 return Ty->getPointeeType();149 return Ty;150 }151 152 // Is the derived type that we're casting from the type of `*this`? In this153 // special case, we can upcast to the base class even if the base is154 // non-public.155 bool CastingFromThis = isa<CXXThisExpr>(Cast->getSubExpr());156 157 // Find the least-derived type in the path (i.e. the last entry in the list)158 // that we can access.159 const CXXBaseSpecifier *PublicBase = nullptr;160 for (const CXXBaseSpecifier *Base : Cast->path()) {161 if (Base->getAccessSpecifier() != AS_public && !CastingFromThis)162 break;163 PublicBase = Base;164 CastingFromThis = false;165 }166 167 if (PublicBase != nullptr)168 return PublicBase->getType();169 170 // We didn't find any public type that we could cast to. There may be more171 // casts in `getSubExpr()`, so recurse. (If there aren't any more casts, this172 // will return the type of `getSubExpr()`.)173 return getPublicType(Cast->getSubExpr());174}175 176// Returns the least-derived type for the receiver of `MCE` that177// `MCE.getImplicitObjectArgument()->IgnoreParentImpCasts()` can be downcast to.178// Effectively, we upcast until we reach a non-public base class, unless that179// base is a base of `*this`.180//181// This is needed to correctly match methods called on types derived from182// `std::optional`.183//184// Say we have a `struct Derived : public std::optional<int> {} d;` For a call185// `d.has_value()`, the `getImplicitObjectArgument()` looks like this:186//187// ImplicitCastExpr 'const std::__optional_storage_base<int>' lvalue188// | <UncheckedDerivedToBase (optional -> __optional_storage_base)>189// `-DeclRefExpr 'Derived' lvalue Var 'd' 'Derived'190//191// The type of the implicit object argument is `__optional_storage_base`192// (since this is the internal type that `has_value()` is declared on). If we193// call `IgnoreParenImpCasts()` on the implicit object argument, we get the194// `DeclRefExpr`, which has type `Derived`. Neither of these types is195// `optional`, and hence neither is sufficient for querying whether we are196// calling a method on `optional`.197//198// Instead, starting with the most derived type, we need to follow the chain of199// casts200QualType getPublicReceiverType(const CXXMemberCallExpr &MCE) {201 return getPublicType(MCE.getImplicitObjectArgument());202}203 204AST_MATCHER_P(CXXMemberCallExpr, publicReceiverType,205 ast_matchers::internal::Matcher<QualType>, InnerMatcher) {206 return InnerMatcher.matches(getPublicReceiverType(Node), Finder, Builder);207}208 209auto isOptionalMemberCallWithNameMatcher(210 ast_matchers::internal::Matcher<NamedDecl> matcher,211 const std::optional<StatementMatcher> &Ignorable = std::nullopt) {212 return cxxMemberCallExpr(Ignorable ? on(expr(unless(*Ignorable)))213 : anything(),214 publicReceiverType(desugarsToOptionalType()),215 callee(cxxMethodDecl(matcher)));216}217 218auto isOptionalOperatorCallWithName(219 llvm::StringRef operator_name,220 const std::optional<StatementMatcher> &Ignorable = std::nullopt) {221 return cxxOperatorCallExpr(222 hasOverloadedOperatorName(operator_name),223 callee(cxxMethodDecl(ofClass(optionalClass()))),224 Ignorable ? callExpr(unless(hasArgument(0, *Ignorable))) : callExpr());225}226 227auto isMakeOptionalCall() {228 return callExpr(229 callee(functionDecl(hasAnyName(230 "std::make_optional", "base::make_optional", "absl::make_optional",231 "folly::make_optional", "bsl::make_optional"))),232 hasOptionalType());233}234 235auto nulloptTypeDecl() {236 return namedDecl(hasAnyName("std::nullopt_t", "absl::nullopt_t",237 "base::nullopt_t", "folly::None",238 "bsl::nullopt_t"));239}240 241auto hasNulloptType() { return hasType(nulloptTypeDecl()); }242 243auto inPlaceClass() {244 return namedDecl(hasAnyName("std::in_place_t", "absl::in_place_t",245 "base::in_place_t", "folly::in_place_t",246 "bsl::in_place_t"));247}248 249auto isOptionalNulloptConstructor() {250 return cxxConstructExpr(251 hasDeclaration(cxxConstructorDecl(parameterCountIs(1),252 hasParameter(0, hasNulloptType()))),253 hasOptionalOrDerivedType());254}255 256auto isOptionalInPlaceConstructor() {257 return cxxConstructExpr(hasArgument(0, hasType(inPlaceClass())),258 hasOptionalOrDerivedType());259}260 261auto isOptionalValueOrConversionConstructor() {262 return cxxConstructExpr(263 unless(hasDeclaration(264 cxxConstructorDecl(anyOf(isCopyConstructor(), isMoveConstructor())))),265 argumentCountIs(1), hasArgument(0, unless(hasNulloptType())),266 hasOptionalOrDerivedType());267}268 269auto isOptionalValueOrConversionAssignment() {270 return cxxOperatorCallExpr(271 hasOverloadedOperatorName("="),272 callee(cxxMethodDecl(ofClass(optionalOrDerivedClass()))),273 unless(hasDeclaration(cxxMethodDecl(274 anyOf(isCopyAssignmentOperator(), isMoveAssignmentOperator())))),275 argumentCountIs(2), hasArgument(1, unless(hasNulloptType())));276}277 278auto isOptionalNulloptAssignment() {279 return cxxOperatorCallExpr(280 hasOverloadedOperatorName("="),281 callee(cxxMethodDecl(ofClass(optionalOrDerivedClass()))),282 argumentCountIs(2), hasArgument(1, hasNulloptType()));283}284 285auto isStdSwapCall() {286 return callExpr(callee(functionDecl(hasName("std::swap"))),287 argumentCountIs(2),288 hasArgument(0, hasOptionalOrDerivedType()),289 hasArgument(1, hasOptionalOrDerivedType()));290}291 292auto isStdForwardCall() {293 return callExpr(callee(functionDecl(hasName("std::forward"))),294 argumentCountIs(1),295 hasArgument(0, hasOptionalOrDerivedType()));296}297 298constexpr llvm::StringLiteral ValueOrCallID = "ValueOrCall";299 300auto isValueOrStringEmptyCall() {301 // `opt.value_or("").empty()`302 return cxxMemberCallExpr(303 callee(cxxMethodDecl(hasName("empty"))),304 onImplicitObjectArgument(ignoringImplicit(305 cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))),306 callee(cxxMethodDecl(hasName("value_or"),307 ofClass(optionalClass()))),308 hasArgument(0, stringLiteral(hasSize(0))))309 .bind(ValueOrCallID))));310}311 312auto isValueOrNotEqX() {313 auto ComparesToSame = [](ast_matchers::internal::Matcher<Stmt> Arg) {314 return hasOperands(315 ignoringImplicit(316 cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))),317 callee(cxxMethodDecl(hasName("value_or"),318 ofClass(optionalClass()))),319 hasArgument(0, Arg))320 .bind(ValueOrCallID)),321 ignoringImplicit(Arg));322 };323 324 // `opt.value_or(X) != X`, for X is `nullptr`, `""`, or `0`. Ideally, we'd325 // support this pattern for any expression, but the AST does not have a326 // generic expression comparison facility, so we specialize to common cases327 // seen in practice. FIXME: define a matcher that compares values across328 // nodes, which would let us generalize this to any `X`.329 return binaryOperation(hasOperatorName("!="),330 anyOf(ComparesToSame(cxxNullPtrLiteralExpr()),331 ComparesToSame(stringLiteral(hasSize(0))),332 ComparesToSame(integerLiteral(equals(0)))));333}334 335auto isZeroParamConstMemberCall() {336 return cxxMemberCallExpr(337 callee(cxxMethodDecl(parameterCountIs(0), isConst())));338}339 340auto isZeroParamConstMemberOperatorCall() {341 return cxxOperatorCallExpr(342 callee(cxxMethodDecl(parameterCountIs(0), isConst())));343}344 345auto isNonConstMemberCall() {346 return cxxMemberCallExpr(callee(cxxMethodDecl(unless(isConst()))));347}348 349auto isNonConstMemberOperatorCall() {350 return cxxOperatorCallExpr(callee(cxxMethodDecl(unless(isConst()))));351}352 353auto isCallReturningOptional() {354 return callExpr(hasType(qualType(355 anyOf(desugarsToOptionalOrDerivedType(),356 referenceType(pointee(desugarsToOptionalOrDerivedType()))))));357}358 359template <typename L, typename R>360auto isComparisonOperatorCall(L lhs_arg_matcher, R rhs_arg_matcher) {361 return cxxOperatorCallExpr(362 anyOf(hasOverloadedOperatorName("=="), hasOverloadedOperatorName("!=")),363 argumentCountIs(2), hasArgument(0, lhs_arg_matcher),364 hasArgument(1, rhs_arg_matcher));365}366 367/// Ensures that `Expr` is mapped to a `BoolValue` and returns its formula.368const Formula &forceBoolValue(Environment &Env, const Expr &Expr) {369 auto *Value = Env.get<BoolValue>(Expr);370 if (Value != nullptr)371 return Value->formula();372 373 Value = &Env.makeAtomicBoolValue();374 Env.setValue(Expr, *Value);375 return Value->formula();376}377 378StorageLocation &locForHasValue(const RecordStorageLocation &OptionalLoc) {379 return OptionalLoc.getSyntheticField("has_value");380}381 382StorageLocation &locForValue(const RecordStorageLocation &OptionalLoc) {383 return OptionalLoc.getSyntheticField("value");384}385 386/// Sets `HasValueVal` as the symbolic value that represents the "has_value"387/// property of the optional at `OptionalLoc`.388void setHasValue(RecordStorageLocation &OptionalLoc, BoolValue &HasValueVal,389 Environment &Env) {390 Env.setValue(locForHasValue(OptionalLoc), HasValueVal);391}392 393/// Returns the symbolic value that represents the "has_value" property of the394/// optional at `OptionalLoc`. Returns null if `OptionalLoc` is null.395BoolValue *getHasValue(Environment &Env, RecordStorageLocation *OptionalLoc) {396 if (OptionalLoc == nullptr)397 return nullptr;398 StorageLocation &HasValueLoc = locForHasValue(*OptionalLoc);399 auto *HasValueVal = Env.get<BoolValue>(HasValueLoc);400 if (HasValueVal == nullptr) {401 HasValueVal = &Env.makeAtomicBoolValue();402 Env.setValue(HasValueLoc, *HasValueVal);403 }404 return HasValueVal;405}406 407QualType valueTypeFromOptionalDecl(const CXXRecordDecl &RD) {408 auto &CTSD = cast<ClassTemplateSpecializationDecl>(RD);409 return CTSD.getTemplateArgs()[0].getAsType();410}411 412/// Returns the number of optional wrappers in `Type`.413///414/// For example, if `Type` is `optional<optional<int>>`, the result of this415/// function will be 2.416int countOptionalWrappers(const ASTContext &ASTCtx, QualType Type) {417 const CXXRecordDecl *Optional =418 getOptionalBaseClass(Type->getAsCXXRecordDecl());419 if (Optional == nullptr)420 return 0;421 return 1 + countOptionalWrappers(422 ASTCtx,423 valueTypeFromOptionalDecl(*Optional).getDesugaredType(ASTCtx));424}425 426StorageLocation *getLocBehindPossiblePointer(const Expr &E,427 const Environment &Env) {428 if (E.isPRValue()) {429 if (auto *PointerVal = dyn_cast_or_null<PointerValue>(Env.getValue(E)))430 return &PointerVal->getPointeeLoc();431 return nullptr;432 }433 return Env.getStorageLocation(E);434}435 436void transferUnwrapCall(const Expr *UnwrapExpr, const Expr *ObjectExpr,437 LatticeTransferState &State) {438 if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>(439 getLocBehindPossiblePointer(*ObjectExpr, State.Env))) {440 if (State.Env.getStorageLocation(*UnwrapExpr) == nullptr)441 State.Env.setStorageLocation(*UnwrapExpr, locForValue(*OptionalLoc));442 }443}444 445void transferArrowOpCall(const Expr *UnwrapExpr, const Expr *ObjectExpr,446 LatticeTransferState &State) {447 if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>(448 getLocBehindPossiblePointer(*ObjectExpr, State.Env)))449 State.Env.setValue(450 *UnwrapExpr, State.Env.create<PointerValue>(locForValue(*OptionalLoc)));451}452 453void transferMakeOptionalCall(const CallExpr *E,454 const MatchFinder::MatchResult &,455 LatticeTransferState &State) {456 setHasValue(State.Env.getResultObjectLocation(*E),457 State.Env.getBoolLiteralValue(true), State.Env);458}459 460void transferOptionalHasValueCall(const CXXMemberCallExpr *CallExpr,461 const MatchFinder::MatchResult &,462 LatticeTransferState &State) {463 if (auto *HasValueVal = getHasValue(464 State.Env, getImplicitObjectLocation(*CallExpr, State.Env))) {465 State.Env.setValue(*CallExpr, *HasValueVal);466 }467}468 469void transferOptionalIsNullCall(const CXXMemberCallExpr *CallExpr,470 const MatchFinder::MatchResult &,471 LatticeTransferState &State) {472 if (auto *HasValueVal = getHasValue(473 State.Env, getImplicitObjectLocation(*CallExpr, State.Env))) {474 State.Env.setValue(*CallExpr, State.Env.makeNot(*HasValueVal));475 }476}477 478/// `ModelPred` builds a logical formula relating the predicate in479/// `ValueOrPredExpr` to the optional's `has_value` property.480void transferValueOrImpl(481 const clang::Expr *ValueOrPredExpr, const MatchFinder::MatchResult &Result,482 LatticeTransferState &State,483 const Formula &(*ModelPred)(Environment &Env, const Formula &ExprVal,484 const Formula &HasValueVal)) {485 auto &Env = State.Env;486 487 const auto *MCE =488 Result.Nodes.getNodeAs<clang::CXXMemberCallExpr>(ValueOrCallID);489 490 auto *HasValueVal =491 getHasValue(State.Env, getImplicitObjectLocation(*MCE, State.Env));492 if (HasValueVal == nullptr)493 return;494 495 Env.assume(ModelPred(Env, forceBoolValue(Env, *ValueOrPredExpr),496 HasValueVal->formula()));497}498 499void transferValueOrStringEmptyCall(const clang::Expr *ComparisonExpr,500 const MatchFinder::MatchResult &Result,501 LatticeTransferState &State) {502 return transferValueOrImpl(ComparisonExpr, Result, State,503 [](Environment &Env, const Formula &ExprVal,504 const Formula &HasValueVal) -> const Formula & {505 auto &A = Env.arena();506 // If the result is *not* empty, then we know the507 // optional must have been holding a value. If508 // `ExprVal` is true, though, we don't learn509 // anything definite about `has_value`, so we510 // don't add any corresponding implications to511 // the flow condition.512 return A.makeImplies(A.makeNot(ExprVal),513 HasValueVal);514 });515}516 517void transferValueOrNotEqX(const Expr *ComparisonExpr,518 const MatchFinder::MatchResult &Result,519 LatticeTransferState &State) {520 transferValueOrImpl(ComparisonExpr, Result, State,521 [](Environment &Env, const Formula &ExprVal,522 const Formula &HasValueVal) -> const Formula & {523 auto &A = Env.arena();524 // We know that if `(opt.value_or(X) != X)` then525 // `opt.hasValue()`, even without knowing further526 // details about the contents of `opt`.527 return A.makeImplies(ExprVal, HasValueVal);528 });529}530 531void transferCallReturningOptional(const CallExpr *E,532 const MatchFinder::MatchResult &Result,533 LatticeTransferState &State) {534 RecordStorageLocation *Loc = nullptr;535 if (E->isPRValue()) {536 Loc = &State.Env.getResultObjectLocation(*E);537 } else {538 Loc = State.Env.get<RecordStorageLocation>(*E);539 if (Loc == nullptr) {540 Loc = &cast<RecordStorageLocation>(State.Env.createStorageLocation(*E));541 State.Env.setStorageLocation(*E, *Loc);542 }543 }544 545 if (State.Env.getValue(locForHasValue(*Loc)) != nullptr)546 return;547 548 setHasValue(*Loc, State.Env.makeAtomicBoolValue(), State.Env);549}550 551// Returns true if the const accessor is handled by caching.552// Returns false if we could not cache. We should perform default handling553// in that case.554bool handleConstMemberCall(const CallExpr *CE,555 dataflow::RecordStorageLocation *RecordLoc,556 const MatchFinder::MatchResult &Result,557 LatticeTransferState &State) {558 if (RecordLoc == nullptr)559 return false;560 561 // Cache if the const method returns a reference.562 if (CE->isGLValue()) {563 const FunctionDecl *DirectCallee = CE->getDirectCallee();564 if (DirectCallee == nullptr)565 return false;566 567 // Initialize the optional's "has_value" property to true if the type is568 // optional, otherwise no-op. If we want to support const ref to pointers or569 // bools we should initialize their values here too.570 auto Init = [&](StorageLocation &Loc) {571 if (isSupportedOptionalType(CE->getType()))572 setHasValue(cast<RecordStorageLocation>(Loc),573 State.Env.makeAtomicBoolValue(), State.Env);574 };575 StorageLocation &Loc =576 State.Lattice.getOrCreateConstMethodReturnStorageLocation(577 *RecordLoc, DirectCallee, State.Env, Init);578 579 State.Env.setStorageLocation(*CE, Loc);580 return true;581 }582 // PRValue cases:583 if (CE->getType()->isBooleanType() || CE->getType()->isPointerType()) {584 // If the const method returns a boolean or pointer type.585 Value *Val = State.Lattice.getOrCreateConstMethodReturnValue(*RecordLoc, CE,586 State.Env);587 if (Val == nullptr)588 return false;589 State.Env.setValue(*CE, *Val);590 return true;591 }592 if (isSupportedOptionalType(CE->getType())) {593 // If the const method returns an optional by value.594 const FunctionDecl *DirectCallee = CE->getDirectCallee();595 if (DirectCallee == nullptr)596 return false;597 StorageLocation &Loc =598 State.Lattice.getOrCreateConstMethodReturnStorageLocation(599 *RecordLoc, DirectCallee, State.Env, [&](StorageLocation &Loc) {600 setHasValue(cast<RecordStorageLocation>(Loc),601 State.Env.makeAtomicBoolValue(), State.Env);602 });603 // Use copyRecord to link the optional to the result object of the call604 // expression.605 auto &ResultLoc = State.Env.getResultObjectLocation(*CE);606 copyRecord(cast<RecordStorageLocation>(Loc), ResultLoc, State.Env);607 return true;608 }609 610 return false;611}612 613void handleConstMemberCallWithFallbacks(614 const CallExpr *CE, dataflow::RecordStorageLocation *RecordLoc,615 const MatchFinder::MatchResult &Result, LatticeTransferState &State) {616 if (handleConstMemberCall(CE, RecordLoc, Result, State))617 return;618 // Perform default handling if the call returns an optional, but wasn't619 // handled by caching.620 if (isSupportedOptionalType(CE->getType()))621 transferCallReturningOptional(CE, Result, State);622}623 624void transferConstMemberCall(const CXXMemberCallExpr *MCE,625 const MatchFinder::MatchResult &Result,626 LatticeTransferState &State) {627 handleConstMemberCallWithFallbacks(628 MCE, dataflow::getImplicitObjectLocation(*MCE, State.Env), Result, State);629}630 631void transferConstMemberOperatorCall(const CXXOperatorCallExpr *OCE,632 const MatchFinder::MatchResult &Result,633 LatticeTransferState &State) {634 auto *RecordLoc = cast_or_null<dataflow::RecordStorageLocation>(635 State.Env.getStorageLocation(*OCE->getArg(0)));636 handleConstMemberCallWithFallbacks(OCE, RecordLoc, Result, State);637}638 639void handleNonConstMemberCall(const CallExpr *CE,640 dataflow::RecordStorageLocation *RecordLoc,641 const MatchFinder::MatchResult &Result,642 LatticeTransferState &State) {643 if (RecordLoc != nullptr) {644 // When a non-const member function is called, clear all (non-const)645 // optional fields of the receiver. Const-qualified fields can't be646 // changed (at least, not without UB).647 for (const auto &[Field, FieldLoc] : RecordLoc->children()) {648 QualType FieldType = Field->getType();649 if (!FieldType.isConstQualified() &&650 isSupportedOptionalType(Field->getType())) {651 auto *FieldRecordLoc = cast_or_null<RecordStorageLocation>(FieldLoc);652 if (FieldRecordLoc) {653 setHasValue(*FieldRecordLoc, State.Env.makeAtomicBoolValue(),654 State.Env);655 }656 }657 }658 State.Lattice.clearConstMethodReturnValues(*RecordLoc);659 State.Lattice.clearConstMethodReturnStorageLocations(*RecordLoc);660 }661 662 // Perform default handling if the call returns an optional.663 if (isSupportedOptionalType(CE->getType())) {664 transferCallReturningOptional(CE, Result, State);665 }666}667 668void transferValue_NonConstMemberCall(const CXXMemberCallExpr *MCE,669 const MatchFinder::MatchResult &Result,670 LatticeTransferState &State) {671 handleNonConstMemberCall(672 MCE, dataflow::getImplicitObjectLocation(*MCE, State.Env), Result, State);673}674 675void transferValue_NonConstMemberOperatorCall(676 const CXXOperatorCallExpr *OCE, const MatchFinder::MatchResult &Result,677 LatticeTransferState &State) {678 auto *RecordLoc = cast_or_null<dataflow::RecordStorageLocation>(679 State.Env.getStorageLocation(*OCE->getArg(0)));680 handleNonConstMemberCall(OCE, RecordLoc, Result, State);681}682 683void constructOptionalValue(const Expr &E, Environment &Env,684 BoolValue &HasValueVal) {685 RecordStorageLocation &Loc = Env.getResultObjectLocation(E);686 setHasValue(Loc, HasValueVal, Env);687}688 689/// Returns a symbolic value for the "has_value" property of an `optional<T>`690/// value that is constructed/assigned from a value of type `U` or `optional<U>`691/// where `T` is constructible from `U`.692BoolValue &valueOrConversionHasValue(QualType DestType, const Expr &E,693 const MatchFinder::MatchResult &MatchRes,694 LatticeTransferState &State) {695 const int DestTypeOptionalWrappersCount =696 countOptionalWrappers(*MatchRes.Context, DestType);697 const int ArgTypeOptionalWrappersCount = countOptionalWrappers(698 *MatchRes.Context, E.getType().getNonReferenceType());699 700 // Is this an constructor of the form `template<class U> optional(U &&)` /701 // assignment of the form `template<class U> optional& operator=(U &&)`702 // (where `T` is assignable / constructible from `U`)?703 // We recognize this because the number of optionals in the optional being704 // assigned to is different from the function argument type.705 if (DestTypeOptionalWrappersCount != ArgTypeOptionalWrappersCount)706 return State.Env.getBoolLiteralValue(true);707 708 // Otherwise, this must be a constructor of the form709 // `template <class U> optional<optional<U> &&)` / assignment of the form710 // `template <class U> optional& operator=(optional<U> &&)711 // (where, again, `T` is assignable / constructible from `U`).712 auto *Loc = State.Env.get<RecordStorageLocation>(E);713 if (auto *HasValueVal = getHasValue(State.Env, Loc))714 return *HasValueVal;715 return State.Env.makeAtomicBoolValue();716}717 718void transferValueOrConversionConstructor(719 const CXXConstructExpr *E, const MatchFinder::MatchResult &MatchRes,720 LatticeTransferState &State) {721 assert(E->getNumArgs() > 0);722 723 constructOptionalValue(724 *E, State.Env,725 valueOrConversionHasValue(726 E->getConstructor()->getThisType()->getPointeeType(), *E->getArg(0),727 MatchRes, State));728}729 730void transferAssignment(const CXXOperatorCallExpr *E, BoolValue &HasValueVal,731 LatticeTransferState &State) {732 assert(E->getNumArgs() > 0);733 734 if (auto *Loc = State.Env.get<RecordStorageLocation>(*E->getArg(0))) {735 setHasValue(*Loc, HasValueVal, State.Env);736 737 // Assign a storage location for the whole expression.738 State.Env.setStorageLocation(*E, *Loc);739 }740}741 742void transferValueOrConversionAssignment(743 const CXXOperatorCallExpr *E, const MatchFinder::MatchResult &MatchRes,744 LatticeTransferState &State) {745 assert(E->getNumArgs() > 1);746 transferAssignment(747 E,748 valueOrConversionHasValue(E->getArg(0)->getType().getNonReferenceType(),749 *E->getArg(1), MatchRes, State),750 State);751}752 753void transferNulloptAssignment(const CXXOperatorCallExpr *E,754 const MatchFinder::MatchResult &,755 LatticeTransferState &State) {756 transferAssignment(E, State.Env.getBoolLiteralValue(false), State);757}758 759void transferSwap(RecordStorageLocation *Loc1, RecordStorageLocation *Loc2,760 Environment &Env) {761 // We account for cases where one or both of the optionals are not modeled,762 // either lacking associated storage locations, or lacking values associated763 // to such storage locations.764 765 if (Loc1 == nullptr) {766 if (Loc2 != nullptr)767 setHasValue(*Loc2, Env.makeAtomicBoolValue(), Env);768 return;769 }770 if (Loc2 == nullptr) {771 setHasValue(*Loc1, Env.makeAtomicBoolValue(), Env);772 return;773 }774 775 // Both expressions have locations, though they may not have corresponding776 // values. In that case, we create a fresh value at this point. Note that if777 // two branches both do this, they will not share the value, but it at least778 // allows for local reasoning about the value. To avoid the above, we would779 // need *lazy* value allocation.780 // FIXME: allocate values lazily, instead of just creating a fresh value.781 BoolValue *BoolVal1 = getHasValue(Env, Loc1);782 if (BoolVal1 == nullptr)783 BoolVal1 = &Env.makeAtomicBoolValue();784 785 BoolValue *BoolVal2 = getHasValue(Env, Loc2);786 if (BoolVal2 == nullptr)787 BoolVal2 = &Env.makeAtomicBoolValue();788 789 setHasValue(*Loc1, *BoolVal2, Env);790 setHasValue(*Loc2, *BoolVal1, Env);791}792 793void transferSwapCall(const CXXMemberCallExpr *E,794 const MatchFinder::MatchResult &,795 LatticeTransferState &State) {796 assert(E->getNumArgs() == 1);797 auto *OtherLoc = State.Env.get<RecordStorageLocation>(*E->getArg(0));798 transferSwap(getImplicitObjectLocation(*E, State.Env), OtherLoc, State.Env);799}800 801void transferStdSwapCall(const CallExpr *E, const MatchFinder::MatchResult &,802 LatticeTransferState &State) {803 assert(E->getNumArgs() == 2);804 auto *Arg0Loc = State.Env.get<RecordStorageLocation>(*E->getArg(0));805 auto *Arg1Loc = State.Env.get<RecordStorageLocation>(*E->getArg(1));806 transferSwap(Arg0Loc, Arg1Loc, State.Env);807}808 809void transferStdForwardCall(const CallExpr *E, const MatchFinder::MatchResult &,810 LatticeTransferState &State) {811 assert(E->getNumArgs() == 1);812 813 if (auto *Loc = State.Env.getStorageLocation(*E->getArg(0)))814 State.Env.setStorageLocation(*E, *Loc);815}816 817const Formula &evaluateEquality(Arena &A, const Formula &EqVal,818 const Formula &LHS, const Formula &RHS) {819 // Logically, an optional<T> object is composed of two values - a `has_value`820 // bit and a value of type T. Equality of optional objects compares both821 // values. Therefore, merely comparing the `has_value` bits isn't sufficient:822 // when two optional objects are engaged, the equality of their respective823 // values of type T matters. Since we only track the `has_value` bits, we824 // can't make any conclusions about equality when we know that two optional825 // objects are engaged.826 //827 // We express this as two facts about the equality:828 // a) EqVal => (LHS & RHS) v (!RHS & !LHS)829 // If they are equal, then either both are set or both are unset.830 // b) (!LHS & !RHS) => EqVal831 // If neither is set, then they are equal.832 // We rewrite b) as !EqVal => (LHS v RHS), for a more compact formula.833 return A.makeAnd(834 A.makeImplies(EqVal, A.makeOr(A.makeAnd(LHS, RHS),835 A.makeAnd(A.makeNot(LHS), A.makeNot(RHS)))),836 A.makeImplies(A.makeNot(EqVal), A.makeOr(LHS, RHS)));837}838 839void transferOptionalAndOptionalCmp(const clang::CXXOperatorCallExpr *CmpExpr,840 const MatchFinder::MatchResult &,841 LatticeTransferState &State) {842 Environment &Env = State.Env;843 auto &A = Env.arena();844 auto *CmpValue = &forceBoolValue(Env, *CmpExpr);845 auto *Arg0Loc = Env.get<RecordStorageLocation>(*CmpExpr->getArg(0));846 if (auto *LHasVal = getHasValue(Env, Arg0Loc)) {847 auto *Arg1Loc = Env.get<RecordStorageLocation>(*CmpExpr->getArg(1));848 if (auto *RHasVal = getHasValue(Env, Arg1Loc)) {849 if (CmpExpr->getOperator() == clang::OO_ExclaimEqual)850 CmpValue = &A.makeNot(*CmpValue);851 Env.assume(evaluateEquality(A, *CmpValue, LHasVal->formula(),852 RHasVal->formula()));853 }854 }855}856 857void transferOptionalAndValueCmp(const clang::CXXOperatorCallExpr *CmpExpr,858 const clang::Expr *E, Environment &Env) {859 auto &A = Env.arena();860 auto *CmpValue = &forceBoolValue(Env, *CmpExpr);861 auto *Loc = Env.get<RecordStorageLocation>(*E);862 if (auto *HasVal = getHasValue(Env, Loc)) {863 if (CmpExpr->getOperator() == clang::OO_ExclaimEqual)864 CmpValue = &A.makeNot(*CmpValue);865 Env.assume(866 evaluateEquality(A, *CmpValue, HasVal->formula(), A.makeLiteral(true)));867 }868}869 870void transferOptionalAndNulloptCmp(const clang::CXXOperatorCallExpr *CmpExpr,871 const clang::Expr *E, Environment &Env) {872 auto &A = Env.arena();873 auto *CmpValue = &forceBoolValue(Env, *CmpExpr);874 auto *Loc = Env.get<RecordStorageLocation>(*E);875 if (auto *HasVal = getHasValue(Env, Loc)) {876 if (CmpExpr->getOperator() == clang::OO_ExclaimEqual)877 CmpValue = &A.makeNot(*CmpValue);878 Env.assume(evaluateEquality(A, *CmpValue, HasVal->formula(),879 A.makeLiteral(false)));880 }881}882 883std::optional<StatementMatcher>884ignorableOptional(const UncheckedOptionalAccessModelOptions &Options) {885 if (Options.IgnoreSmartPointerDereference) {886 auto SmartPtrUse = expr(ignoringParenImpCasts(cxxOperatorCallExpr(887 anyOf(hasOverloadedOperatorName("->"), hasOverloadedOperatorName("*")),888 unless(hasArgument(0, expr(hasOptionalType()))))));889 return expr(890 anyOf(SmartPtrUse, memberExpr(hasObjectExpression(SmartPtrUse))));891 }892 return std::nullopt;893}894 895StatementMatcher896valueCall(const std::optional<StatementMatcher> &IgnorableOptional) {897 return isOptionalMemberCallWithNameMatcher(hasName("value"),898 IgnorableOptional);899}900 901StatementMatcher902valueOperatorCall(const std::optional<StatementMatcher> &IgnorableOptional) {903 return expr(anyOf(isOptionalOperatorCallWithName("*", IgnorableOptional),904 isOptionalOperatorCallWithName("->", IgnorableOptional)));905}906 907auto buildTransferMatchSwitch() {908 // FIXME: Evaluate the efficiency of matchers. If using matchers results in a909 // lot of duplicated work (e.g. string comparisons), consider providing APIs910 // that avoid it through memoization.911 return CFGMatchSwitchBuilder<LatticeTransferState>()912 // make_optional913 .CaseOfCFGStmt<CallExpr>(isMakeOptionalCall(), transferMakeOptionalCall)914 915 // optional::optional (in place)916 .CaseOfCFGStmt<CXXConstructExpr>(917 isOptionalInPlaceConstructor(),918 [](const CXXConstructExpr *E, const MatchFinder::MatchResult &,919 LatticeTransferState &State) {920 constructOptionalValue(*E, State.Env,921 State.Env.getBoolLiteralValue(true));922 })923 // optional::optional(nullopt_t)924 .CaseOfCFGStmt<CXXConstructExpr>(925 isOptionalNulloptConstructor(),926 [](const CXXConstructExpr *E, const MatchFinder::MatchResult &,927 LatticeTransferState &State) {928 constructOptionalValue(*E, State.Env,929 State.Env.getBoolLiteralValue(false));930 })931 // optional::optional (value/conversion)932 .CaseOfCFGStmt<CXXConstructExpr>(isOptionalValueOrConversionConstructor(),933 transferValueOrConversionConstructor)934 935 // optional::operator=936 .CaseOfCFGStmt<CXXOperatorCallExpr>(937 isOptionalValueOrConversionAssignment(),938 transferValueOrConversionAssignment)939 .CaseOfCFGStmt<CXXOperatorCallExpr>(isOptionalNulloptAssignment(),940 transferNulloptAssignment)941 942 // optional::value943 .CaseOfCFGStmt<CXXMemberCallExpr>(944 valueCall(std::nullopt),945 [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,946 LatticeTransferState &State) {947 transferUnwrapCall(E, E->getImplicitObjectArgument(), State);948 })949 950 // optional::operator*951 .CaseOfCFGStmt<CallExpr>(isOptionalOperatorCallWithName("*"),952 [](const CallExpr *E,953 const MatchFinder::MatchResult &,954 LatticeTransferState &State) {955 transferUnwrapCall(E, E->getArg(0), State);956 })957 958 // optional::operator->959 .CaseOfCFGStmt<CallExpr>(isOptionalOperatorCallWithName("->"),960 [](const CallExpr *E,961 const MatchFinder::MatchResult &,962 LatticeTransferState &State) {963 transferArrowOpCall(E, E->getArg(0), State);964 })965 966 // optional::has_value, optional::hasValue967 // Of the supported optionals only folly::Optional uses hasValue, but this968 // will also pass for other types969 .CaseOfCFGStmt<CXXMemberCallExpr>(970 isOptionalMemberCallWithNameMatcher(971 hasAnyName("has_value", "hasValue")),972 transferOptionalHasValueCall)973 974 // optional::operator bool975 .CaseOfCFGStmt<CXXMemberCallExpr>(976 isOptionalMemberCallWithNameMatcher(hasName("operator bool")),977 transferOptionalHasValueCall)978 979 // NullableValue::isNull980 // Only NullableValue has isNull981 .CaseOfCFGStmt<CXXMemberCallExpr>(982 isOptionalMemberCallWithNameMatcher(hasName("isNull")),983 transferOptionalIsNullCall)984 985 // NullableValue::makeValue, NullableValue::makeValueInplace986 // Only NullableValue has these methods, but this987 // will also pass for other types988 .CaseOfCFGStmt<CXXMemberCallExpr>(989 isOptionalMemberCallWithNameMatcher(990 hasAnyName("makeValue", "makeValueInplace")),991 [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,992 LatticeTransferState &State) {993 if (RecordStorageLocation *Loc =994 getImplicitObjectLocation(*E, State.Env)) {995 setHasValue(*Loc, State.Env.getBoolLiteralValue(true), State.Env);996 }997 })998 999 // optional::emplace1000 .CaseOfCFGStmt<CXXMemberCallExpr>(1001 isOptionalMemberCallWithNameMatcher(hasName("emplace")),1002 [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,1003 LatticeTransferState &State) {1004 if (RecordStorageLocation *Loc =1005 getImplicitObjectLocation(*E, State.Env)) {1006 setHasValue(*Loc, State.Env.getBoolLiteralValue(true), State.Env);1007 }1008 })1009 1010 // optional::reset1011 .CaseOfCFGStmt<CXXMemberCallExpr>(1012 isOptionalMemberCallWithNameMatcher(hasName("reset")),1013 [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,1014 LatticeTransferState &State) {1015 if (RecordStorageLocation *Loc =1016 getImplicitObjectLocation(*E, State.Env)) {1017 setHasValue(*Loc, State.Env.getBoolLiteralValue(false),1018 State.Env);1019 }1020 })1021 1022 // optional::swap1023 .CaseOfCFGStmt<CXXMemberCallExpr>(1024 isOptionalMemberCallWithNameMatcher(hasName("swap")),1025 transferSwapCall)1026 1027 // std::swap1028 .CaseOfCFGStmt<CallExpr>(isStdSwapCall(), transferStdSwapCall)1029 1030 // std::forward1031 .CaseOfCFGStmt<CallExpr>(isStdForwardCall(), transferStdForwardCall)1032 1033 // opt.value_or("").empty()1034 .CaseOfCFGStmt<Expr>(isValueOrStringEmptyCall(),1035 transferValueOrStringEmptyCall)1036 1037 // opt.value_or(X) != X1038 .CaseOfCFGStmt<Expr>(isValueOrNotEqX(), transferValueOrNotEqX)1039 1040 // Comparisons (==, !=):1041 .CaseOfCFGStmt<CXXOperatorCallExpr>(1042 isComparisonOperatorCall(hasOptionalType(), hasOptionalType()),1043 transferOptionalAndOptionalCmp)1044 .CaseOfCFGStmt<CXXOperatorCallExpr>(1045 isComparisonOperatorCall(hasOptionalType(), hasNulloptType()),1046 [](const clang::CXXOperatorCallExpr *Cmp,1047 const MatchFinder::MatchResult &, LatticeTransferState &State) {1048 transferOptionalAndNulloptCmp(Cmp, Cmp->getArg(0), State.Env);1049 })1050 .CaseOfCFGStmt<CXXOperatorCallExpr>(1051 isComparisonOperatorCall(hasNulloptType(), hasOptionalType()),1052 [](const clang::CXXOperatorCallExpr *Cmp,1053 const MatchFinder::MatchResult &, LatticeTransferState &State) {1054 transferOptionalAndNulloptCmp(Cmp, Cmp->getArg(1), State.Env);1055 })1056 .CaseOfCFGStmt<CXXOperatorCallExpr>(1057 isComparisonOperatorCall(1058 hasOptionalType(),1059 unless(anyOf(hasOptionalType(), hasNulloptType()))),1060 [](const clang::CXXOperatorCallExpr *Cmp,1061 const MatchFinder::MatchResult &, LatticeTransferState &State) {1062 transferOptionalAndValueCmp(Cmp, Cmp->getArg(0), State.Env);1063 })1064 .CaseOfCFGStmt<CXXOperatorCallExpr>(1065 isComparisonOperatorCall(1066 unless(anyOf(hasOptionalType(), hasNulloptType())),1067 hasOptionalType()),1068 [](const clang::CXXOperatorCallExpr *Cmp,1069 const MatchFinder::MatchResult &, LatticeTransferState &State) {1070 transferOptionalAndValueCmp(Cmp, Cmp->getArg(1), State.Env);1071 })1072 1073 // Smart-pointer-like operator* and operator-> calls that may look like1074 // const accessors (below) but need special handling to allow mixing1075 // the accessor calls.1076 .CaseOfCFGStmt<CXXOperatorCallExpr>(1077 isSmartPointerLikeOperatorStar(),1078 [](const CXXOperatorCallExpr *E,1079 const MatchFinder::MatchResult &Result,1080 LatticeTransferState &State) {1081 transferSmartPointerLikeCachedDeref(1082 E,1083 dyn_cast_or_null<RecordStorageLocation>(1084 getLocBehindPossiblePointer(*E->getArg(0), State.Env)),1085 State, [](StorageLocation &Loc) {});1086 })1087 .CaseOfCFGStmt<CXXOperatorCallExpr>(1088 isSmartPointerLikeOperatorArrow(),1089 [](const CXXOperatorCallExpr *E,1090 const MatchFinder::MatchResult &Result,1091 LatticeTransferState &State) {1092 transferSmartPointerLikeCachedGet(1093 E,1094 dyn_cast_or_null<RecordStorageLocation>(1095 getLocBehindPossiblePointer(*E->getArg(0), State.Env)),1096 State, [](StorageLocation &Loc) {});1097 })1098 .CaseOfCFGStmt<CXXMemberCallExpr>(1099 isSmartPointerLikeValueMethodCall(),1100 [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &Result,1101 LatticeTransferState &State) {1102 transferSmartPointerLikeCachedDeref(1103 E, getImplicitObjectLocation(*E, State.Env), State,1104 [](StorageLocation &Loc) {});1105 })1106 .CaseOfCFGStmt<CXXMemberCallExpr>(1107 isSmartPointerLikeGetMethodCall(),1108 [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &Result,1109 LatticeTransferState &State) {1110 transferSmartPointerLikeCachedGet(1111 E, getImplicitObjectLocation(*E, State.Env), State,1112 [](StorageLocation &Loc) {});1113 })1114 1115 // const accessor calls1116 .CaseOfCFGStmt<CXXMemberCallExpr>(isZeroParamConstMemberCall(),1117 transferConstMemberCall)1118 .CaseOfCFGStmt<CXXOperatorCallExpr>(isZeroParamConstMemberOperatorCall(),1119 transferConstMemberOperatorCall)1120 // non-const member calls that may modify the state of an object.1121 .CaseOfCFGStmt<CXXMemberCallExpr>(isNonConstMemberCall(),1122 transferValue_NonConstMemberCall)1123 .CaseOfCFGStmt<CXXOperatorCallExpr>(1124 isNonConstMemberOperatorCall(),1125 transferValue_NonConstMemberOperatorCall)1126 1127 // other cases of returning optional1128 .CaseOfCFGStmt<CallExpr>(isCallReturningOptional(),1129 transferCallReturningOptional)1130 1131 .Build();1132}1133 1134llvm::SmallVector<UncheckedOptionalAccessDiagnostic>1135diagnoseUnwrapCall(const Expr *ObjectExpr, const Environment &Env) {1136 if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>(1137 getLocBehindPossiblePointer(*ObjectExpr, Env))) {1138 auto *Prop = Env.getValue(locForHasValue(*OptionalLoc));1139 if (auto *HasValueVal = cast_or_null<BoolValue>(Prop)) {1140 if (Env.proves(HasValueVal->formula()))1141 return {};1142 }1143 }1144 1145 // Record that this unwrap is *not* provably safe.1146 // FIXME: include the name of the optional (if applicable).1147 auto Range = CharSourceRange::getTokenRange(ObjectExpr->getSourceRange());1148 return {UncheckedOptionalAccessDiagnostic{Range}};1149}1150 1151auto buildDiagnoseMatchSwitch(1152 const UncheckedOptionalAccessModelOptions &Options) {1153 // FIXME: Evaluate the efficiency of matchers. If using matchers results in a1154 // lot of duplicated work (e.g. string comparisons), consider providing APIs1155 // that avoid it through memoization.1156 const auto IgnorableOptional = ignorableOptional(Options);1157 1158 auto DiagBuilder =1159 CFGMatchSwitchBuilder<1160 const Environment,1161 llvm::SmallVector<UncheckedOptionalAccessDiagnostic>>()1162 // optional::operator*, optional::operator->1163 .CaseOfCFGStmt<CallExpr>(1164 valueOperatorCall(IgnorableOptional),1165 [](const CallExpr *E, const MatchFinder::MatchResult &,1166 const Environment &Env) {1167 return diagnoseUnwrapCall(E->getArg(0), Env);1168 });1169 1170 auto Builder = Options.IgnoreValueCalls1171 ? std::move(DiagBuilder)1172 : std::move(DiagBuilder)1173 // optional::value1174 .CaseOfCFGStmt<CXXMemberCallExpr>(1175 valueCall(IgnorableOptional),1176 [](const CXXMemberCallExpr *E,1177 const MatchFinder::MatchResult &,1178 const Environment &Env) {1179 return diagnoseUnwrapCall(1180 E->getImplicitObjectArgument(), Env);1181 });1182 1183 return std::move(Builder).Build();1184}1185 1186} // namespace1187 1188ast_matchers::DeclarationMatcher1189UncheckedOptionalAccessModel::optionalClassDecl() {1190 return cxxRecordDecl(optionalClass());1191}1192 1193UncheckedOptionalAccessModel::UncheckedOptionalAccessModel(ASTContext &Ctx,1194 Environment &Env)1195 : DataflowAnalysis<UncheckedOptionalAccessModel,1196 UncheckedOptionalAccessLattice>(Ctx),1197 TransferMatchSwitch(buildTransferMatchSwitch()) {1198 Env.getDataflowAnalysisContext().setSyntheticFieldCallback(1199 [&Ctx](QualType Ty) -> llvm::StringMap<QualType> {1200 const CXXRecordDecl *Optional =1201 getOptionalBaseClass(Ty->getAsCXXRecordDecl());1202 if (Optional == nullptr)1203 return {};1204 return {{"value", valueTypeFromOptionalDecl(*Optional)},1205 {"has_value", Ctx.BoolTy}};1206 });1207}1208 1209void UncheckedOptionalAccessModel::transfer(const CFGElement &Elt,1210 UncheckedOptionalAccessLattice &L,1211 Environment &Env) {1212 LatticeTransferState State(L, Env);1213 TransferMatchSwitch(Elt, getASTContext(), State);1214}1215 1216UncheckedOptionalAccessDiagnoser::UncheckedOptionalAccessDiagnoser(1217 UncheckedOptionalAccessModelOptions Options)1218 : DiagnoseMatchSwitch(buildDiagnoseMatchSwitch(Options)) {}1219 1220} // namespace dataflow1221} // namespace clang1222