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