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