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