clang 24.0.0git
UncheckedOptionalAccessModel.cpp
Go to the documentation of this file.
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
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"
23#include "clang/Analysis/CFG.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>
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>(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>(RD.getDeclContext()))
70 return N->isStdNamespace() ||
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>(RD.getDeclContext());
78 return N != nullptr && (isFullyQualifiedNamespaceEqualTo(*N, "base") ||
80 }
81
82 if (RD.getName() == "Optional_Base") {
83 const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());
84 return N != nullptr &&
85 isFullyQualifiedNamespaceEqualTo(*N, "bslstl", "BloombergLP");
86 }
87
88 if (RD.getName() == "NullableValue") {
89 const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());
90 return N != nullptr &&
91 isFullyQualifiedNamespaceEqualTo(*N, "bdlb", "BloombergLP");
92 }
93
94 return false;
95}
96
98 if (RD == nullptr)
99 return nullptr;
100 if (hasOptionalClassName(*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(Base.getType()->getAsCXXRecordDecl()))
109 return BaseClass;
110
111 return nullptr;
112}
113
115 const CXXRecordDecl *Optional =
117 return Optional != nullptr;
118}
119
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>(RD->getDeclContext()))
127 return isFullyQualifiedNamespaceEqualTo(*N, "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(Node); }
139
140AST_MATCHER(CXXRecordDecl, optionalOrDerivedClass) {
141 return getOptionalBaseClass(&Node) != nullptr;
142}
143
144auto desugarsToOptionalType() {
145 return hasUnqualifiedDesugaredType(
146 recordType(hasDeclaration(cxxRecordDecl(optionalClass()))));
147}
148
149auto desugarsToOptionalOrDerivedType() {
150 return hasUnqualifiedDesugaredType(
151 recordType(hasDeclaration(cxxRecordDecl(optionalOrDerivedClass()))));
152}
153
154auto hasOptionalType() { return hasType(desugarsToOptionalType()); }
155
156/// Matches any of the spellings of the optional types and sugar, aliases,
157/// derived classes, etc.
158auto hasOptionalOrDerivedType() {
159 return hasType(desugarsToOptionalOrDerivedType());
160}
161
162bool isDesugaredTypeOptional(QualType Ty) {
163 const Type &DesugaredTy = *Ty->getUnqualifiedDesugaredType();
164 return DesugaredTy.isRecordType() &&
165 hasOptionalClassName(*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>(E->IgnoreParens());
201 if (Cast == nullptr || Cast->getCastKind() != CK_UncheckedDerivedToBase)
202 return isDesugaredTypeOptionalOrPointerToOptional(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(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(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(Cast->getSubExpr());
221}
222
223AST_MATCHER(CXXMemberCallExpr, hasOptionalReceiverType) {
224 return hasReceiverTypeDesugaringToOptional(Node.getImplicitObjectArgument());
225}
226
227AST_MATCHER(CXXOperatorCallExpr, hasOptionalOperatorObjectType) {
228 return hasReceiverTypeDesugaringToOptional(Node.getArg(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(expr(unless(*Ignorable))) : anything(),
236 callee(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(operator_name), hasOptionalOperatorObjectType(),
244 Ignorable ? callExpr(unless(hasArgument(0, *Ignorable))) : callExpr());
245}
246
247auto isMakeOptionalCall() {
248 return callExpr(
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(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(allocatorArgClass()); }
274
275auto isOptionalNulloptConstructor() {
276 return cxxConstructExpr(
277 hasDeclaration(cxxConstructorDecl(parameterCountIs(1),
278 hasParameter(0, hasNulloptType()))),
279 hasOptionalOrDerivedType());
280}
281
282auto isOptionalInPlaceConstructor() {
283 return cxxConstructExpr(hasAnyArgument(hasType(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(
292 cxxConstructorDecl(anyOf(isCopyConstructor(), isMoveConstructor())))),
293 argumentCountAtLeast(1),
294 hasArgument(0, unless(anyOf(hasNulloptType(), hasType(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(
305 cxxConstructorDecl(anyOf(isCopyConstructor(), isMoveConstructor())))),
306 hasArgument(0, hasAllocatorArgType()), argumentCountAtLeast(3),
307 hasArgument(2, unless(anyOf(hasNulloptType(), hasType(inPlaceClass())))),
308 hasOptionalOrDerivedType());
309}
310
311auto isOptionalValueOrConversionAssignment() {
312 return cxxOperatorCallExpr(
314 callee(cxxMethodDecl(ofClass(optionalOrDerivedClass()))),
316 anyOf(isCopyAssignmentOperator(), isMoveAssignmentOperator())))),
317 argumentCountIs(2), hasArgument(1, unless(hasNulloptType())));
318}
319
320auto isOptionalNulloptAssignment() {
321 return cxxOperatorCallExpr(
323 callee(cxxMethodDecl(ofClass(optionalOrDerivedClass()))),
324 argumentCountIs(2), hasArgument(1, hasNulloptType()));
325}
326
327auto isStdSwapCall() {
328 return callExpr(callee(functionDecl(hasName("std::swap"))),
329 argumentCountIs(2),
330 hasArgument(0, hasOptionalOrDerivedType()),
331 hasArgument(1, hasOptionalOrDerivedType()));
332}
333
334auto isStdForwardCall() {
335 return callExpr(callee(functionDecl(hasName("std::forward"))),
336 argumentCountIs(1),
337 hasArgument(0, hasOptionalOrDerivedType()));
338}
339
340auto isAssertionResultOperatorBoolCall() {
341 return cxxMemberCallExpr(
342 on(expr(unless(cxxThisExpr()))),
343 callee(cxxMethodDecl(hasName("operator bool"),
344 ofClass(hasName("testing::AssertionResult")))));
345}
346
347auto isAssertionResultConstructFromBoolCall() {
348 return cxxConstructExpr(
349 hasType(recordDecl(hasName("testing::AssertionResult"))),
350 hasArgument(0, hasType(booleanType())));
351}
352
353auto isAssertionResultConstructFromOptionalCall() {
354 return cxxConstructExpr(
355 hasType(recordDecl(hasName("testing::AssertionResult"))),
356 hasArgument(0, hasOptionalOrDerivedType()));
357}
358
359constexpr llvm::StringLiteral ValueOrCallID = "ValueOrCall";
360
361auto isValueOrStringEmptyCall() {
362 // `opt.value_or("").empty()`
363 return cxxMemberCallExpr(
364 callee(cxxMethodDecl(hasName("empty"))),
365 onImplicitObjectArgument(ignoringImplicit(
367 callee(cxxMethodDecl(hasName("value_or"),
368 ofClass(optionalClass()))),
369 hasArgument(0, stringLiteral(hasSize(0))))
370 .bind(ValueOrCallID))));
371}
372
373auto isValueOrNotEqX() {
374 auto ComparesToSame = [](ast_matchers::internal::Matcher<Stmt> Arg) {
375 return hasOperands(
376 ignoringImplicit(
378 callee(cxxMethodDecl(hasName("value_or"),
379 ofClass(optionalClass()))),
380 hasArgument(0, Arg))
381 .bind(ValueOrCallID)),
382 ignoringImplicit(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("!="),
391 anyOf(ComparesToSame(cxxNullPtrLiteralExpr()),
392 ComparesToSame(stringLiteral(hasSize(0))),
393 ComparesToSame(integerLiteral(equals(0)))));
394}
395
396auto isZeroParamConstMemberCall() {
397 return cxxMemberCallExpr(
398 callee(cxxMethodDecl(parameterCountIs(0), isConst())));
399}
400
401auto isZeroParamConstMemberOperatorCall() {
402 return cxxOperatorCallExpr(
403 callee(cxxMethodDecl(parameterCountIs(0), isConst())));
404}
405
406auto isNonConstMemberCall() {
407 return cxxMemberCallExpr(callee(cxxMethodDecl(unless(isConst()))));
408}
409
410auto isNonConstMemberOperatorCall() {
411 return cxxOperatorCallExpr(callee(cxxMethodDecl(unless(isConst()))));
412}
413
414auto isCallReturningOptional() {
415 return callExpr(hasType(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(
424 argumentCountIs(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>(Expr);
431 if (Value != nullptr)
432 return Value->formula();
433
434 Value = &Env.makeAtomicBoolValue();
435 Env.setValue(Expr, *Value);
436 return Value->formula();
437}
438
439StorageLocation &locForHasValue(const RecordStorageLocation &OptionalLoc) {
440 return OptionalLoc.getSyntheticField("has_value");
441}
442
443StorageLocation &locForValue(const RecordStorageLocation &OptionalLoc) {
444 return OptionalLoc.getSyntheticField("value");
445}
446
448locForAssertResultSuccess(const RecordStorageLocation &AssertResultLoc) {
449 return AssertResultLoc.getSyntheticField("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(locForHasValue(OptionalLoc), 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);
465 auto *HasValueVal = Env.get<BoolValue>(HasValueLoc);
466 if (HasValueVal == nullptr) {
467 HasValueVal = &Env.makeAtomicBoolValue();
468 Env.setValue(HasValueLoc, *HasValueVal);
469 }
470 return HasValueVal;
471}
472
473QualType valueTypeFromOptionalDecl(const CXXRecordDecl &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(Type->getAsCXXRecordDecl());
485 if (Optional == nullptr)
486 return 0;
487 return 1 + countOptionalWrappers(
488 ASTCtx,
489 valueTypeFromOptionalDecl(*Optional).getDesugaredType(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>(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 getLocBehindPossiblePointer(*ObjectExpr, State.Env))) {
506 if (State.Env.getStorageLocation(*UnwrapExpr) == nullptr)
507 State.Env.setStorageLocation(*UnwrapExpr, locForValue(*OptionalLoc));
508 }
509}
510
511void transferArrowOpCall(const Expr *UnwrapExpr, const Expr *ObjectExpr,
512 LatticeTransferState &State) {
513 if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>(
514 getLocBehindPossiblePointer(*ObjectExpr, State.Env)))
515 State.Env.setValue(
516 *UnwrapExpr, State.Env.create<PointerValue>(locForValue(*OptionalLoc)));
517}
518
519void transferMakeOptionalCall(const CallExpr *E,
520 const MatchFinder::MatchResult &,
521 LatticeTransferState &State) {
522 setHasValue(State.Env.getResultObjectLocation(*E),
523 State.Env.getBoolLiteralValue(true), State.Env);
524}
525
526void transferOptionalHasValueCall(const CXXMemberCallExpr *CallExpr,
527 const MatchFinder::MatchResult &,
528 LatticeTransferState &State) {
529 if (auto *HasValueVal = getHasValue(
530 State.Env, getImplicitObjectLocation(*CallExpr, State.Env))) {
531 State.Env.setValue(*CallExpr, *HasValueVal);
532 }
533}
534
535void transferOptionalIsNullCall(const CXXMemberCallExpr *CallExpr,
536 const MatchFinder::MatchResult &,
537 LatticeTransferState &State) {
538 if (auto *HasValueVal = getHasValue(
539 State.Env, getImplicitObjectLocation(*CallExpr, State.Env))) {
540 State.Env.setValue(*CallExpr, State.Env.makeNot(*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>(ValueOrCallID);
555
556 auto *HasValueVal =
557 getHasValue(State.Env, getImplicitObjectLocation(*MCE, State.Env));
558 if (HasValueVal == nullptr)
559 return;
560
561 Env.assume(ModelPred(Env, forceBoolValue(Env, *ValueOrPredExpr),
562 HasValueVal->formula()));
563}
564
565void transferValueOrStringEmptyCall(const clang::Expr *ComparisonExpr,
566 const MatchFinder::MatchResult &Result,
567 LatticeTransferState &State) {
568 return transferValueOrImpl(ComparisonExpr, Result, State,
569 [](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(A.makeNot(ExprVal),
579 HasValueVal);
580 });
581}
582
583void transferValueOrNotEqX(const Expr *ComparisonExpr,
584 const MatchFinder::MatchResult &Result,
585 LatticeTransferState &State) {
586 transferValueOrImpl(ComparisonExpr, Result, State,
587 [](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(ExprVal, 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(*E);
603 } else {
604 Loc = State.Env.get<RecordStorageLocation>(*E);
605 if (Loc == nullptr) {
606 Loc = &cast<RecordStorageLocation>(State.Env.createStorageLocation(*E));
607 State.Env.setStorageLocation(*E, *Loc);
608 }
609 }
610
611 if (State.Env.getValue(locForHasValue(*Loc)) != nullptr)
612 return;
613
614 setHasValue(*Loc, State.Env.makeAtomicBoolValue(), 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(CE->getType()))
638 setHasValue(cast<RecordStorageLocation>(Loc),
639 State.Env.makeAtomicBoolValue(), State.Env);
640 };
641 StorageLocation &Loc =
642 State.Lattice.getOrCreateConstMethodReturnStorageLocation(
643 *RecordLoc, DirectCallee, State.Env, Init);
644
645 State.Env.setStorageLocation(*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, CE,
652 State.Env);
653 if (Val == nullptr)
654 return false;
655 State.Env.setValue(*CE, *Val);
656 return true;
657 }
658 if (isSupportedOptionalType(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, DirectCallee, State.Env, [&](StorageLocation &Loc) {
666 setHasValue(cast<RecordStorageLocation>(Loc),
667 State.Env.makeAtomicBoolValue(), 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(*CE);
672 copyRecord(cast<RecordStorageLocation>(Loc), ResultLoc, 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(CE->getType()))
687 transferCallReturningOptional(CE, Result, State);
688}
689
690void transferConstMemberCall(const CXXMemberCallExpr *MCE,
691 const MatchFinder::MatchResult &Result,
692 LatticeTransferState &State) {
693 handleConstMemberCallWithFallbacks(
694 MCE, dataflow::getImplicitObjectLocation(*MCE, 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 State.Env.getStorageLocation(*OCE->getArg(0)));
702 handleConstMemberCallWithFallbacks(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(Field->getType())) {
717 auto *FieldRecordLoc = cast_or_null<RecordStorageLocation>(FieldLoc);
718 if (FieldRecordLoc) {
719 setHasValue(*FieldRecordLoc, State.Env.makeAtomicBoolValue(),
720 State.Env);
721 }
722 }
723 }
724 State.Lattice.clearConstMethodReturnValues(*RecordLoc);
725 State.Lattice.clearConstMethodReturnStorageLocations(*RecordLoc);
726 }
727
728 // Perform default handling if the call returns an optional.
729 if (isSupportedOptionalType(CE->getType())) {
730 transferCallReturningOptional(CE, Result, State);
731 }
732}
733
734void transferValue_NonConstMemberCall(const CXXMemberCallExpr *MCE,
735 const MatchFinder::MatchResult &Result,
736 LatticeTransferState &State) {
737 handleNonConstMemberCall(
738 MCE, dataflow::getImplicitObjectLocation(*MCE, 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 State.Env.getStorageLocation(*OCE->getArg(0)));
746 handleNonConstMemberCall(OCE, RecordLoc, Result, State);
747}
748
749void constructOptionalValue(const Expr &E, Environment &Env,
750 BoolValue &HasValueVal) {
751 RecordStorageLocation &Loc = Env.getResultObjectLocation(E);
752 setHasValue(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(*MatchRes.Context, DestType);
763 const int ArgTypeOptionalWrappersCount = countOptionalWrappers(
764 *MatchRes.Context, 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(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(State.Env, 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, State.Env,
791 valueOrConversionHasValue(
792 E->getConstructor()->getThisType()->getPointeeType(),
793 *E->getArg(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->getArg(0))) {
815 setHasValue(*Loc, HasValueVal, State.Env);
816
817 // Assign a storage location for the whole expression.
818 State.Env.setStorageLocation(*E, *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 valueOrConversionHasValue(E->getArg(0)->getType().getNonReferenceType(),
829 *E->getArg(1), MatchRes, State),
830 State);
831}
832
833void transferNulloptAssignment(const CXXOperatorCallExpr *E,
834 const MatchFinder::MatchResult &,
835 LatticeTransferState &State) {
836 transferAssignment(E, State.Env.getBoolLiteralValue(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(*Loc2, Env.makeAtomicBoolValue(), Env);
848 return;
849 }
850 if (Loc2 == nullptr) {
851 setHasValue(*Loc1, 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, Loc1);
862 if (BoolVal1 == nullptr)
863 BoolVal1 = &Env.makeAtomicBoolValue();
864
865 BoolValue *BoolVal2 = getHasValue(Env, Loc2);
866 if (BoolVal2 == nullptr)
867 BoolVal2 = &Env.makeAtomicBoolValue();
868
869 setHasValue(*Loc1, *BoolVal2, Env);
870 setHasValue(*Loc2, *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->getArg(0));
878 transferSwap(getImplicitObjectLocation(*E, State.Env), OtherLoc, 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->getArg(0));
885 auto *Arg1Loc = State.Env.get<RecordStorageLocation>(*E->getArg(1));
886 transferSwap(Arg0Loc, Arg1Loc, 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->getArg(0)))
894 State.Env.setStorageLocation(*E, *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)
909 // b) (!LHS & !RHS) => EqVal
910 // If neither is set, then they are equal.
911 // We rewrite b) as !EqVal => (LHS v RHS), for a more compact formula.
912 return A.makeAnd(A.makeImplies(EqVal, A.makeEquals(LHS, RHS)),
913 A.makeImplies(A.makeNot(EqVal), A.makeOr(LHS, RHS)));
914}
915
916void transferOptionalAndOptionalCmp(const clang::CXXOperatorCallExpr *CmpExpr,
917 const MatchFinder::MatchResult &,
918 LatticeTransferState &State) {
919 Environment &Env = State.Env;
920 auto &A = Env.arena();
921 auto *CmpValue = &forceBoolValue(Env, *CmpExpr);
922 auto *Arg0Loc = Env.get<RecordStorageLocation>(*CmpExpr->getArg(0));
923 if (auto *LHasVal = getHasValue(Env, Arg0Loc)) {
924 auto *Arg1Loc = Env.get<RecordStorageLocation>(*CmpExpr->getArg(1));
925 if (auto *RHasVal = getHasValue(Env, Arg1Loc)) {
926 if (CmpExpr->getOperator() == clang::OO_ExclaimEqual)
927 CmpValue = &A.makeNot(*CmpValue);
928 Env.assume(evaluateEquality(A, *CmpValue, LHasVal->formula(),
929 RHasVal->formula()));
930 }
931 }
932}
933
934void transferOptionalAndValueCmp(const clang::CXXOperatorCallExpr *CmpExpr,
935 const clang::Expr *E, Environment &Env) {
936 auto &A = Env.arena();
937 auto *CmpValue = &forceBoolValue(Env, *CmpExpr);
938 auto *Loc = Env.get<RecordStorageLocation>(*E);
939 if (auto *HasVal = getHasValue(Env, Loc)) {
940 if (CmpExpr->getOperator() == clang::OO_ExclaimEqual)
941 CmpValue = &A.makeNot(*CmpValue);
942 Env.assume(
943 evaluateEquality(A, *CmpValue, HasVal->formula(), A.makeLiteral(true)));
944 }
945}
946
947void transferOptionalAndNulloptCmp(const clang::CXXOperatorCallExpr *CmpExpr,
948 const clang::Expr *E, Environment &Env) {
949 auto &A = Env.arena();
950 auto *CmpValue = &forceBoolValue(Env, *CmpExpr);
951 auto *Loc = Env.get<RecordStorageLocation>(*E);
952 if (auto *HasVal = getHasValue(Env, Loc)) {
953 if (CmpExpr->getOperator() == clang::OO_ExclaimEqual)
954 CmpValue = &A.makeNot(*CmpValue);
955 Env.assume(evaluateEquality(A, *CmpValue, HasVal->formula(),
956 A.makeLiteral(false)));
957 }
958}
959
960void transferAssertionResultOperatorBoolCall(const CXXMemberCallExpr *Expr,
961 const MatchFinder::MatchResult &,
962 LatticeTransferState &State) {
963 auto *AssertResultLoc = getImplicitObjectLocation(*Expr, State.Env);
964 if (AssertResultLoc == nullptr)
965 return;
966
967 if (BoolValue *SuccessVal = State.Env.get<BoolValue>(
968 locForAssertResultSuccess(*AssertResultLoc))) {
969 State.Env.setValue(*Expr, *SuccessVal);
970 }
971}
972
973void transferAssertionResultConstructFromBoolCall(
974 const CXXConstructExpr *ConstructExpr, const MatchFinder::MatchResult &,
975 LatticeTransferState &State) {
976 assert(ConstructExpr->getNumArgs() > 0);
977 const Expr *Arg = ConstructExpr->getArg(0)->IgnoreImplicit();
978
979 if (BoolValue *SuccessVal = State.Env.get<BoolValue>(*Arg)) {
980 auto &ResultLoc = State.Env.getResultObjectLocation(*ConstructExpr);
981 State.Env.setValue(locForAssertResultSuccess(ResultLoc), *SuccessVal);
982 }
983}
984
985void transferAssertionResultConstructFromOptionalCall(
986 const CXXConstructExpr *ConstructExpr, const MatchFinder::MatchResult &,
987 LatticeTransferState &State) {
988 assert(ConstructExpr->getNumArgs() > 0);
989
990 const Expr *Arg = ConstructExpr->getArg(0)->IgnoreImplicit();
991 auto *OptionalLoc =
992 cast_or_null<RecordStorageLocation>(State.Env.getStorageLocation(*Arg));
993 if (OptionalLoc == nullptr)
994 return;
995
996 if (BoolValue *HasVal = getHasValue(State.Env, OptionalLoc)) {
997 auto &ResultLoc = State.Env.getResultObjectLocation(*ConstructExpr);
998 State.Env.setValue(locForAssertResultSuccess(ResultLoc), *HasVal);
999 }
1000}
1001
1002std::optional<StatementMatcher>
1003ignorableOptional(const UncheckedOptionalAccessModelOptions &Options) {
1004 if (Options.IgnoreSmartPointerDereference) {
1005 auto SmartPtrUse = expr(ignoringParenImpCasts(cxxOperatorCallExpr(
1007 unless(hasArgument(0, expr(hasOptionalType()))))));
1008 return expr(
1009 anyOf(SmartPtrUse, memberExpr(hasObjectExpression(SmartPtrUse))));
1010 }
1011 return std::nullopt;
1012}
1013
1015valueCall(const std::optional<StatementMatcher> &IgnorableOptional) {
1016 return isOptionalMemberCallWithNameMatcher(hasName("value"),
1017 IgnorableOptional);
1018}
1019
1021valueOperatorCall(const std::optional<StatementMatcher> &IgnorableOptional) {
1022 return expr(anyOf(isOptionalOperatorCallWithName("*", IgnorableOptional),
1023 isOptionalOperatorCallWithName("->", IgnorableOptional)));
1024}
1025
1027 // FIXME: Evaluate the efficiency of matchers. If using matchers results in a
1028 // lot of duplicated work (e.g. string comparisons), consider providing APIs
1029 // that avoid it through memoization.
1031 // make_optional
1032 .CaseOfCFGStmt<CallExpr>(isMakeOptionalCall(), transferMakeOptionalCall)
1033
1034 // optional::optional (in place)
1035 .CaseOfCFGStmt<CXXConstructExpr>(
1036 isOptionalInPlaceConstructor(),
1037 [](const CXXConstructExpr *E, const MatchFinder::MatchResult &,
1038 LatticeTransferState &State) {
1039 constructOptionalValue(*E, State.Env,
1040 State.Env.getBoolLiteralValue(true));
1041 })
1042 // optional::optional(nullopt_t)
1043 .CaseOfCFGStmt<CXXConstructExpr>(
1044 isOptionalNulloptConstructor(),
1045 [](const CXXConstructExpr *E, const MatchFinder::MatchResult &,
1046 LatticeTransferState &State) {
1047 constructOptionalValue(*E, State.Env,
1048 State.Env.getBoolLiteralValue(false));
1049 })
1050 // optional::optional (value/conversion)
1051 .CaseOfCFGStmt<CXXConstructExpr>(isOptionalValueOrConversionConstructor(),
1052 transferValueOrConversionConstructor)
1053 // optional::optional (allocator-extended value/conversion)
1054 .CaseOfCFGStmt<CXXConstructExpr>(
1055 isOptionalAllocatorExtendedValueOrConversionConstructor(),
1056 transferAllocatorExtendedValueOrConversionConstructor)
1057
1058 // optional::operator=
1059 .CaseOfCFGStmt<CXXOperatorCallExpr>(
1060 isOptionalValueOrConversionAssignment(),
1061 transferValueOrConversionAssignment)
1062 .CaseOfCFGStmt<CXXOperatorCallExpr>(isOptionalNulloptAssignment(),
1063 transferNulloptAssignment)
1064
1065 // optional::value
1066 .CaseOfCFGStmt<CXXMemberCallExpr>(
1067 valueCall(std::nullopt),
1068 [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
1069 LatticeTransferState &State) {
1070 transferUnwrapCall(E, E->getImplicitObjectArgument(), State);
1071 })
1072
1073 // optional::operator*
1074 .CaseOfCFGStmt<CallExpr>(isOptionalOperatorCallWithName("*"),
1075 [](const CallExpr *E,
1076 const MatchFinder::MatchResult &,
1077 LatticeTransferState &State) {
1078 transferUnwrapCall(E, E->getArg(0), State);
1079 })
1080
1081 // optional::operator->
1082 .CaseOfCFGStmt<CallExpr>(isOptionalOperatorCallWithName("->"),
1083 [](const CallExpr *E,
1084 const MatchFinder::MatchResult &,
1085 LatticeTransferState &State) {
1086 transferArrowOpCall(E, E->getArg(0), State);
1087 })
1088
1089 // optional::has_value, optional::hasValue
1090 // Of the supported optionals only folly::Optional uses hasValue, but this
1091 // will also pass for other types
1092 .CaseOfCFGStmt<CXXMemberCallExpr>(
1093 isOptionalMemberCallWithNameMatcher(
1094 hasAnyName("has_value", "hasValue")),
1095 transferOptionalHasValueCall)
1096
1097 // optional::operator bool
1098 .CaseOfCFGStmt<CXXMemberCallExpr>(
1099 isOptionalMemberCallWithNameMatcher(hasName("operator bool")),
1100 transferOptionalHasValueCall)
1101
1102 // NullableValue::isNull
1103 // Only NullableValue has isNull
1104 .CaseOfCFGStmt<CXXMemberCallExpr>(
1105 isOptionalMemberCallWithNameMatcher(hasName("isNull")),
1106 transferOptionalIsNullCall)
1107
1108 // NullableValue::makeValue, NullableValue::makeValueInplace
1109 // Only NullableValue has these methods, but this
1110 // will also pass for other types
1111 .CaseOfCFGStmt<CXXMemberCallExpr>(
1112 isOptionalMemberCallWithNameMatcher(
1113 hasAnyName("makeValue", "makeValueInplace")),
1114 [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
1115 LatticeTransferState &State) {
1116 if (RecordStorageLocation *Loc =
1117 getImplicitObjectLocation(*E, State.Env)) {
1118 setHasValue(*Loc, State.Env.getBoolLiteralValue(true), State.Env);
1119 }
1120 })
1121
1122 // optional::emplace
1123 .CaseOfCFGStmt<CXXMemberCallExpr>(
1124 isOptionalMemberCallWithNameMatcher(hasName("emplace")),
1125 [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
1126 LatticeTransferState &State) {
1127 if (RecordStorageLocation *Loc =
1128 getImplicitObjectLocation(*E, State.Env)) {
1129 setHasValue(*Loc, State.Env.getBoolLiteralValue(true), State.Env);
1130 }
1131 })
1132
1133 // optional::reset
1134 .CaseOfCFGStmt<CXXMemberCallExpr>(
1135 isOptionalMemberCallWithNameMatcher(hasName("reset")),
1136 [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
1137 LatticeTransferState &State) {
1138 if (RecordStorageLocation *Loc =
1139 getImplicitObjectLocation(*E, State.Env)) {
1140 setHasValue(*Loc, State.Env.getBoolLiteralValue(false),
1141 State.Env);
1142 }
1143 })
1144
1145 // optional::swap
1146 .CaseOfCFGStmt<CXXMemberCallExpr>(
1147 isOptionalMemberCallWithNameMatcher(hasName("swap")),
1148 transferSwapCall)
1149
1150 // std::swap
1151 .CaseOfCFGStmt<CallExpr>(isStdSwapCall(), transferStdSwapCall)
1152
1153 // std::forward
1154 .CaseOfCFGStmt<CallExpr>(isStdForwardCall(), transferStdForwardCall)
1155
1156 // opt.value_or("").empty()
1157 .CaseOfCFGStmt<Expr>(isValueOrStringEmptyCall(),
1158 transferValueOrStringEmptyCall)
1159
1160 // opt.value_or(X) != X
1161 .CaseOfCFGStmt<Expr>(isValueOrNotEqX(), transferValueOrNotEqX)
1162
1163 // Comparisons (==, !=):
1164 .CaseOfCFGStmt<CXXOperatorCallExpr>(
1165 isComparisonOperatorCall(hasOptionalType(), hasOptionalType()),
1166 transferOptionalAndOptionalCmp)
1167 .CaseOfCFGStmt<CXXOperatorCallExpr>(
1168 isComparisonOperatorCall(hasOptionalType(), hasNulloptType()),
1169 [](const clang::CXXOperatorCallExpr *Cmp,
1170 const MatchFinder::MatchResult &, LatticeTransferState &State) {
1171 transferOptionalAndNulloptCmp(Cmp, Cmp->getArg(0), State.Env);
1172 })
1173 .CaseOfCFGStmt<CXXOperatorCallExpr>(
1174 isComparisonOperatorCall(hasNulloptType(), hasOptionalType()),
1175 [](const clang::CXXOperatorCallExpr *Cmp,
1176 const MatchFinder::MatchResult &, LatticeTransferState &State) {
1177 transferOptionalAndNulloptCmp(Cmp, Cmp->getArg(1), State.Env);
1178 })
1179 .CaseOfCFGStmt<CXXOperatorCallExpr>(
1180 isComparisonOperatorCall(
1181 hasOptionalType(),
1182 unless(anyOf(hasOptionalType(), hasNulloptType()))),
1183 [](const clang::CXXOperatorCallExpr *Cmp,
1184 const MatchFinder::MatchResult &, LatticeTransferState &State) {
1185 transferOptionalAndValueCmp(Cmp, Cmp->getArg(0), State.Env);
1186 })
1187 .CaseOfCFGStmt<CXXOperatorCallExpr>(
1188 isComparisonOperatorCall(
1189 unless(anyOf(hasOptionalType(), hasNulloptType())),
1190 hasOptionalType()),
1191 [](const clang::CXXOperatorCallExpr *Cmp,
1192 const MatchFinder::MatchResult &, LatticeTransferState &State) {
1193 transferOptionalAndValueCmp(Cmp, Cmp->getArg(1), State.Env);
1194 })
1195
1196 // Smart-pointer-like operator* and operator-> calls that may look like
1197 // const accessors (below) but need special handling to allow mixing
1198 // the accessor calls.
1199 .CaseOfCFGStmt<CXXOperatorCallExpr>(
1201 [](const CXXOperatorCallExpr *E,
1202 const MatchFinder::MatchResult &Result,
1203 LatticeTransferState &State) {
1205 E,
1206 dyn_cast_or_null<RecordStorageLocation>(
1207 getLocBehindPossiblePointer(*E->getArg(0), State.Env)),
1208 State, [](StorageLocation &Loc) {});
1209 })
1210 .CaseOfCFGStmt<CXXOperatorCallExpr>(
1212 [](const CXXOperatorCallExpr *E,
1213 const MatchFinder::MatchResult &Result,
1214 LatticeTransferState &State) {
1216 E,
1217 dyn_cast_or_null<RecordStorageLocation>(
1218 getLocBehindPossiblePointer(*E->getArg(0), State.Env)),
1219 State, [](StorageLocation &Loc) {});
1220 })
1221 .CaseOfCFGStmt<CXXMemberCallExpr>(
1223 [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &Result,
1224 LatticeTransferState &State) {
1226 E, getImplicitObjectLocation(*E, State.Env), State,
1227 [](StorageLocation &Loc) {});
1228 })
1229 .CaseOfCFGStmt<CXXMemberCallExpr>(
1231 [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &Result,
1232 LatticeTransferState &State) {
1234 E, getImplicitObjectLocation(*E, State.Env), State,
1235 [](StorageLocation &Loc) {});
1236 })
1237
1238 // gtest
1239 .CaseOfCFGStmt<CXXMemberCallExpr>(isAssertionResultOperatorBoolCall(),
1240 transferAssertionResultOperatorBoolCall)
1241 .CaseOfCFGStmt<CXXMemberCallExpr>(
1243 [](const CXXMemberCallExpr *Expr, const MatchFinder::MatchResult &,
1244 LatticeTransferState &State) {
1246 Expr, State.Env, locForAssertResultSuccess);
1247 })
1248 .CaseOfCFGStmt<CXXConstructExpr>(
1249 isAssertionResultConstructFromBoolCall(),
1250 transferAssertionResultConstructFromBoolCall)
1251 .CaseOfCFGStmt<CXXConstructExpr>(
1252 isAssertionResultConstructFromOptionalCall(),
1253 transferAssertionResultConstructFromOptionalCall)
1254 // const accessor calls
1255 .CaseOfCFGStmt<CXXMemberCallExpr>(isZeroParamConstMemberCall(),
1256 transferConstMemberCall)
1257 .CaseOfCFGStmt<CXXOperatorCallExpr>(isZeroParamConstMemberOperatorCall(),
1258 transferConstMemberOperatorCall)
1259 // non-const member calls that may modify the state of an object.
1260 .CaseOfCFGStmt<CXXMemberCallExpr>(isNonConstMemberCall(),
1261 transferValue_NonConstMemberCall)
1262 .CaseOfCFGStmt<CXXOperatorCallExpr>(
1263 isNonConstMemberOperatorCall(),
1264 transferValue_NonConstMemberOperatorCall)
1265
1266 // other cases of returning optional
1267 .CaseOfCFGStmt<CallExpr>(isCallReturningOptional(),
1268 transferCallReturningOptional)
1269
1270 .Build();
1271}
1272
1273llvm::SmallVector<UncheckedOptionalAccessDiagnostic>
1274diagnoseUnwrapCall(const Expr *ObjectExpr, const Environment &Env) {
1275 if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>(
1276 getLocBehindPossiblePointer(*ObjectExpr, Env))) {
1277 auto *Prop = Env.getValue(locForHasValue(*OptionalLoc));
1278 if (auto *HasValueVal = cast_or_null<BoolValue>(Prop)) {
1279 if (Env.proves(HasValueVal->formula()))
1280 return {};
1281 }
1282 }
1283
1284 // Record that this unwrap is *not* provably safe.
1285 // FIXME: include the name of the optional (if applicable).
1286 auto Range = CharSourceRange::getTokenRange(ObjectExpr->getSourceRange());
1288}
1289
1290auto buildDiagnoseMatchSwitch(
1291 const UncheckedOptionalAccessModelOptions &Options) {
1292 // FIXME: Evaluate the efficiency of matchers. If using matchers results in a
1293 // lot of duplicated work (e.g. string comparisons), consider providing APIs
1294 // that avoid it through memoization.
1295 const auto IgnorableOptional = ignorableOptional(Options);
1296
1297 auto DiagBuilder =
1299 const Environment,
1300 llvm::SmallVector<UncheckedOptionalAccessDiagnostic>>()
1301 // optional::operator*, optional::operator->
1302 .CaseOfCFGStmt<CallExpr>(
1303 valueOperatorCall(IgnorableOptional),
1304 [](const CallExpr *E, const MatchFinder::MatchResult &,
1305 const Environment &Env) {
1306 return diagnoseUnwrapCall(E->getArg(0), Env);
1307 });
1308
1309 auto Builder = Options.IgnoreValueCalls
1310 ? std::move(DiagBuilder)
1311 : std::move(DiagBuilder)
1312 // optional::value
1313 .CaseOfCFGStmt<CXXMemberCallExpr>(
1314 valueCall(IgnorableOptional),
1315 [](const CXXMemberCallExpr *E,
1316 const MatchFinder::MatchResult &,
1317 const Environment &Env) {
1318 return diagnoseUnwrapCall(
1319 E->getImplicitObjectArgument(), Env);
1320 });
1321
1322 return std::move(Builder).Build();
1323}
1324
1325} // namespace
1326
1329 return cxxMemberCallExpr(hasOptionalReceiverType());
1330}
1331
1334 return cxxOperatorCallExpr(hasOptionalOperatorObjectType());
1335}
1336
1338 Environment &Env)
1341 TransferMatchSwitch(buildTransferMatchSwitch()) {
1342 Env.getDataflowAnalysisContext().setSyntheticFieldCallback(
1343 [&Ctx](QualType Ty) -> llvm::StringMap<QualType> {
1344 if (isAssertionResultType(Ty))
1345 return {{"success", Ctx.BoolTy}};
1346
1347 const CXXRecordDecl *Optional =
1349 if (Optional == nullptr)
1350 return {};
1351 return {{"value", valueTypeFromOptionalDecl(*Optional)},
1352 {"has_value", Ctx.BoolTy}};
1353 });
1354}
1355
1358 Environment &Env) {
1359 LatticeTransferState State(L, Env);
1360 TransferMatchSwitch(Elt, getASTContext(), State);
1361}
1362
1365 : DiagnoseMatchSwitch(buildDiagnoseMatchSwitch(Options)) {}
1366
1367} // namespace dataflow
1368} // namespace clang
Defines the clang::ASTContext interface.
#define AST_MATCHER(Type, DefineMatcher)
AST_MATCHER(Type, DefineMatcher) { ... } defines a zero parameter function named DefineMatcher() that...
Defines the C++ Decl subclasses, other than those for templates (found in DeclTemplate....
Defines the clang::Expr interface and subclasses for C++ expressions.
llvm::MachO::RecordLoc RecordLoc
Definition MachO.h:41
Defines an enumeration for C++ overloaded operators.
MatchFinder::MatchResult MatchResult
Defines the clang::SourceLocation class and associated facilities.
C Language Family Type Representation.
Holds long-lived AST nodes (such as types and decls) that can be referred to throughout the semantic ...
Definition ASTContext.h:245
CanQualType BoolTy
Represents a top-level expression in a basic block.
Definition CFG.h:55
Represents a base class of a C++ class.
Definition DeclCXX.h:146
OverloadedOperatorKind getOperator() const
Returns the kind of overloaded operator that this expression refers to.
Definition ExprCXX.h:116
Represents a C++ struct/union/class.
Definition DeclCXX.h:258
base_class_range bases()
Definition DeclCXX.h:609
bool hasDefinition() const
Definition DeclCXX.h:562
Expr * getArg(unsigned Arg)
getArg - Return the specified argument.
Definition Expr.h:3195
static CharSourceRange getTokenRange(SourceRange R)
DeclContext * getParent()
getParent - Returns the containing DeclContext.
Definition DeclBase.h:2120
bool isTranslationUnit() const
Definition DeclBase.h:2216
DeclContext * getDeclContext()
Definition DeclBase.h:456
bool isIdentifier() const
Predicate functions for querying what type of name this is.
StringRef getName() const
Get the name of identifier for this declaration as a StringRef.
Definition Decl.h:302
DeclarationName getDeclName() const
Get the actual, stored name of the declaration, which may be a special name.
Definition Decl.h:341
Represent a C++ namespace.
Definition Decl.h:593
A (possibly-)qualified type.
Definition TypeBase.h:938
The base class of the type hierarchy.
Definition TypeBase.h:1879
CXXRecordDecl * getAsCXXRecordDecl() const
Retrieves the CXXRecordDecl that this type refers to, either because the type is a RecordType or beca...
Definition Type.h:26
RecordDecl * getAsRecordDecl() const
Retrieves the RecordDecl this type refers to.
Definition Type.h:41
The Arena owns the objects that model data within an analysis.
Definition Arena.h:21
Models a boolean.
Definition Value.h:94
Collects cases of a "match switch": a collection of matchers paired with callbacks,...
CFGMatchSwitchBuilder && CaseOfCFGStmt(MatchSwitchMatcher< Stmt > M, MatchSwitchAction< NodeT, State, Result > A) &&
Registers an action A for CFGStmts that will be triggered by the match of the pattern M against the S...
CFGMatchSwitch< State, Result > Build() &&
Holds the state of the program (store and heap) at a given program point.
DataflowAnalysisContext & getDataflowAnalysisContext() const
Returns the DataflowAnalysisContext used by the environment.
Models a symbolic pointer. Specifically, any value of type T*.
Definition Value.h:170
A storage location for a record (struct, class, or union).
Base class for elements of the local variable store and of the heap.
UncheckedOptionalAccessDiagnoser(UncheckedOptionalAccessModelOptions Options={})
UncheckedOptionalAccessModel(ASTContext &Ctx, dataflow::Environment &Env)
void transfer(const CFGElement &Elt, UncheckedOptionalAccessLattice &L, Environment &Env)
static ast_matchers::StatementMatcher operatorCallToOptionalClass()
static ast_matchers::StatementMatcher memberCallToOptionalClass()
Returns a matcher for calls to optional classes diagnosed by this model.
Base class for all values computed by abstract interpretation.
Definition Value.h:33
const internal::VariadicOperatorMatcherFunc< 1, 1 > unless
Matches if the provided matcher does not match.
const internal::VariadicDynCastAllOfMatcher< Stmt, StringLiteral > stringLiteral
Matches string literals (also matches wide string literals).
const internal::VariadicDynCastAllOfMatcher< Stmt, CallExpr > callExpr
Matches call expressions.
const internal::VariadicDynCastAllOfMatcher< Decl, NamedDecl > namedDecl
Matches a declaration of anything that could have a name.
internal::TrueMatcher anything()
Matches any node.
const internal::VariadicFunction< internal::Matcher< NamedDecl >, StringRef, internal::hasAnyNameFunc > hasAnyName
Matches NamedDecl nodes that have any of the specified names.
const internal::MapAnyOfMatcher< BinaryOperator, CXXOperatorCallExpr, CXXRewrittenBinaryOperator > binaryOperation
Matches nodes which can be used with binary operators.
const internal::VariadicDynCastAllOfMatcher< Stmt, CXXMemberCallExpr > cxxMemberCallExpr
Matches member call expressions.
const internal::VariadicDynCastAllOfMatcher< Decl, CXXConstructorDecl > cxxConstructorDecl
Matches C++ constructor declarations.
internal::Matcher< Stmt > StatementMatcher
const internal::VariadicDynCastAllOfMatcher< Stmt, CXXConstructExpr > cxxConstructExpr
Matches constructor call expressions (including implicit ones).
const internal::VariadicDynCastAllOfMatcher< Stmt, CXXOperatorCallExpr > cxxOperatorCallExpr
Matches overloaded operator calls.
const internal::VariadicDynCastAllOfMatcher< Decl, FunctionDecl > functionDecl
Matches function declarations.
const AstTypeMatcher< RecordType > recordType
const internal::VariadicDynCastAllOfMatcher< Stmt, MemberExpr > memberExpr
Matches member expressions.
const internal::VariadicDynCastAllOfMatcher< Decl, CXXRecordDecl > cxxRecordDecl
Matches C++ class declarations.
const internal::VariadicDynCastAllOfMatcher< Decl, RecordDecl > recordDecl
Matches class, struct, and union declarations.
const internal::VariadicDynCastAllOfMatcher< Stmt, IntegerLiteral > integerLiteral
Matches integer literals of all sizes / encodings, e.g.
const internal::VariadicDynCastAllOfMatcher< Stmt, CXXNullPtrLiteralExpr > cxxNullPtrLiteralExpr
Matches nullptr literal.
internal::PolymorphicMatcher< internal::HasDeclarationMatcher, void(internal::HasDeclarationSupportedTypes), internal::Matcher< Decl > > hasDeclaration(const internal::Matcher< Decl > &InnerMatcher)
Matches a node if the declaration associated with that node matches the given matcher.
const internal::VariadicDynCastAllOfMatcher< Stmt, Expr > expr
Matches expressions.
const internal::VariadicOperatorMatcherFunc< 2, std::numeric_limits< unsigned >::max()> anyOf
Matches if any of the given matchers matches.
const internal::VariadicAllOfMatcher< QualType > qualType
Matches QualTypes in the clang AST.
const internal::VariadicDynCastAllOfMatcher< Stmt, CXXThisExpr > cxxThisExpr
Matches implicit and explicit this expressions.
void transferAssertionResultExpectationOperatorBoolCall(const CXXMemberCallExpr *Expr, Environment &Env, llvm::function_ref< StorageLocation &(RecordStorageLocation &)> GetOk)
clang::ast_matchers::StatementMatcher isAssertionResultExpectationOperatorBoolCall()
TransferState< UncheckedStatusOrAccessModel::Lattice > LatticeTransferState
CFGMatchSwitch< LatticeTransferState > buildTransferMatchSwitch(ASTContext &Ctx, CFGMatchSwitchBuilder< LatticeTransferState > Builder)
ast_matchers::StatementMatcher isSmartPointerLikeValueMethodCall(clang::StringRef MethodName="value")
static bool isFullyQualifiedNamespaceEqualTo(const NamespaceDecl &NS, llvm::StringRef Name, NameTypes... Names)
void transferSmartPointerLikeCachedDeref(const CallExpr *DerefExpr, RecordStorageLocation *SmartPointerLoc, TransferState< LatticeT > &State, llvm::function_ref< void(StorageLocation &)> InitializeLoc)
A transfer function for operator* (and value) calls that can be cached.
static bool hasOptionalClassName(const CXXRecordDecl &RD)
void copyRecord(RecordStorageLocation &Src, RecordStorageLocation &Dst, Environment &Env, QualType TypeToCopy=QualType())
Copies a record (struct, class, or union) from Src to Dst.
Definition RecordOps.cpp:57
void transferSmartPointerLikeCachedGet(const CallExpr *GetExpr, RecordStorageLocation *SmartPointerLoc, TransferState< LatticeT > &State, llvm::function_ref< void(StorageLocation &)> InitializeLoc)
A transfer function for operator-> (and get) calls that can be cached.
internal::Matcher< NamedDecl > hasName(StringRef Name)
Matches NamedDecl nodes that have the specified name.
static bool isAssertionResultType(QualType Type)
CachedConstAccessorsLattice< NoopLattice > UncheckedOptionalAccessLattice
const internal::VariadicDynCastAllOfMatcher< Stmt, CXXMemberCallExpr > cxxMemberCallExpr
Matches member call expressions.
static bool isSupportedOptionalType(QualType Ty)
ast_matchers::StatementMatcher isSmartPointerLikeOperatorArrow()
const internal::VariadicDynCastAllOfMatcher< Stmt, CXXOperatorCallExpr > cxxOperatorCallExpr
Matches overloaded operator calls.
internal::PolymorphicMatcher< internal::HasOverloadedOperatorNameMatcher, AST_POLYMORPHIC_SUPPORTED_TYPES(CXXOperatorCallExpr, FunctionDecl), std::vector< std::string > > hasOverloadedOperatorName(StringRef Name)
Matches overloaded operator names.
RecordStorageLocation * getImplicitObjectLocation(const CXXMemberCallExpr &MCE, const Environment &Env)
Returns the storage location for the implicit object of a CXXMemberCallExpr, or null if none is defin...
static const CXXRecordDecl * getOptionalBaseClass(const CXXRecordDecl *RD)
ast_matchers::StatementMatcher isSmartPointerLikeOperatorStar()
const internal::VariadicDynCastAllOfMatcher< Decl, CXXMethodDecl > cxxMethodDecl
Matches method declarations.
ast_matchers::StatementMatcher isSmartPointerLikeGetMethodCall(clang::StringRef MethodName="get")
const AstTypeMatcher< ReferenceType > referenceType
bool Cast(InterpState &S, CodePtr OpPC)
Definition Interp.h:2878
bool equals(const til::SExpr *E1, const til::SExpr *E2)
Top level wrappers for InstallAPI frontend operations.
@ Result
The result type of a method or function.
Definition TypeBase.h:906
U cast(CodeGen::Address addr)
Definition Address.h:327
Diagnostic information for an unchecked optional access.