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